def add_to_ready_to_play_queue(self, movie: MovieType) -> None: """ :param movie: :return: """ clz = PlayableTrailersContainer if clz.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): if Movie.TITLE not in movie: clz.logger.warning('Invalid movie entry. Missing title: ', str(movie)) Debug.validate_detailed_movie_properties(movie, stack_trace=False) try: title = movie[Movie.TITLE] if title not in clz._recently_played_trailers: clz._recently_played_trailers[title] = movie if len(clz._recently_played_trailers) > 10: clz._recently_played_trailers.popitem() else: if clz.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): clz.logger.debug_verbose( f'Movie: {title} played recently, skipping') return except Exception as e: clz.logger.exception(e) if clz.logger.isEnabledFor(LazyLogger.DEBUG): clz.logger.debug_verbose('movie:', movie[Movie.TITLE], 'queue empty:', self._ready_to_play_queue.empty(), 'full:', self._ready_to_play_queue.full()) finished = False waited = 0 while not finished: try: self._ready_to_play_queue.put(movie, block=True, timeout=0.05) finished = True self._number_of_added_trailers += 1 except (queue.Full): waited += 1 Monitor.throw_exception_if_abort_requested(timeout=0.5) if not clz._any_trailers_available_to_play.isSet(): if clz.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): clz.logger.debug_verbose( 'Setting _any_trailers_available_to_play') clz._any_trailers_available_to_play.set() self._is_playable_trailers.set() if clz.logger.isEnabledFor(LazyLogger.DEBUG_EXTRA_VERBOSE): clz.logger.debug_extra_verbose('readyToPlayQueue size:', self._ready_to_play_queue.qsize(), 'waited:', waited) return
def _pre_fetch_trailer(self): try: while not Monitor.throw_exception_if_abort_requested(): status, trailer = FrontendBridge.get_next_trailer() if trailer is not None and Debug.validate_detailed_movie_properties( trailer): added = False while not added: try: self._pre_fetched_trailer_queue.put(trailer, timeout=0.1) added = True except queue.Full: if Monitor.throw_exception_if_abort_requested( timeout=0.5): break except AbortException: pass # In thread, let die except Exception as e: self._logger.exception(e)
def _inform_abort_listeners(cls): # type: () ->None """ :return: """ with cls._abort_listener_lock: if cls._abort_listeners_informed: return if cls._logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): cls._logger.enter() listeners_copy = copy.copy(cls._abort_listeners) del cls._abort_listeners[:] # Unregister all cls._abort_listeners_informed = True for listener in listeners_copy: # noinspection PyTypeChecker thread = threading.Thread(target=listener, name='Monitor._inform_abort_listeners') thread.start() # cls._inform_settings_changed_listeners() cls.startup_complete_event.set() with cls._settings_changed_listener_lock: del cls._settings_changed_listeners[:] with cls._screen_saver_listener_lock: del cls._screen_saver_listeners[:] if cls._logger.isEnabledFor(LazyLogger.DEBUG): xbmc.sleep(250) from common.debug_utils import Debug Debug.dump_all_threads() xbmc.sleep(1000) Debug.dump_all_threads() xbmc.sleep(1000) Debug.dump_all_threads()
def discover_basic_information_worker(self, path): # type: (str) -> None """ :param path: :return: """ local_class = DiscoverFolderTrailers try: folders = [] if str(path).startswith('multipath://'): # get all paths from the multipath paths = path[12:-1].split('/') for item in paths: folders.append(requests.utils.unquote_unreserved(item)) else: folders.append(path) DiskUtils.RandomGenerator.shuffle(folders) for folder in folders: Monitor.throw_exception_if_abort_requested() if xbmcvfs.exists(xbmcvfs.translatePath(folder)): # get all files and sub-folders dirs, files = xbmcvfs.listdir(folder) # Assume every file is a movie trailer. Manufacture # a movie name and other info from the filename. DiskUtils.RandomGenerator.shuffle(files) for item in files: try: file_path = os.path.join(folder, item) title = xbmcvfs.translatePath(file_path) # TODO: DELETE ME title = os.path.basename(title) title = os.path.splitext(title)[0] new_trailer = { Movie.TITLE: title, Movie.TRAILER: file_path, Movie.TYPE: 'trailer file', Movie.SOURCE: Movie.FOLDER_SOURCE, Movie.FANART: '', Movie.THUMBNAIL: '', Movie.FILE: '', Movie.YEAR: '' } if local_class.logger.isEnabledFor( LazyLogger.DEBUG): Debug.validate_basic_movie_properties( new_trailer) self.add_to_discovered_trailers(new_trailer) except AbortException: reraise(*sys.exc_info()) except Exception as e: local_class.logger.exception('') for item in dirs: # recursively scan all sub-folders sub_tree = os.path.join(folder, item) self.discover_basic_information_worker(sub_tree) except AbortException: reraise(*sys.exc_info()) except Exception as e: local_class.logger.exception('') return
def _do_next(self): # type: () -> Union[dict, None] """ :return: """ clz = PlayableTrailerService try: while not PlayableTrailersContainer.is_any_trailers_available_to_play( ): self.throw_exception_on_forced_to_stop(movie_data=None, delay=0.25) except Exception as e: self.logger.exception('') total_number_of_trailers = 0 start_time = datetime.datetime.now() # Considered locking all TrailerManagers here to guarantee # that lengths don't change while finding the right trailer # but that might block the readyToPlayQueue from getting # loaded. Besides, it doesn't matter too much if we play # the incorrect trailer, as long as we get one. The # major fear is if we have no trailers at all, but that # will be handled elsewhere. # Get total number of trailers from all managers. # It is possible that all discovery is complete and there is nothing # to play. nothing_to_play = True # playable_trailers_map = None # type: Dict[str, # PlayableTrailersContainer] # type: playable_trailers_map = PlayableTrailersContainer.get_instances() # Dict[str, PlayableTrailersContainer] # Need to use the same projected sizes throughout this method. projected_sizes_map = {} for source in playable_trailers_map: playable_trailers = playable_trailers_map[source] if not playable_trailers.is_playable_trailers(): continue movie_data = playable_trailers.get_movie_data() self.throw_exception_on_forced_to_stop(movie_data=movie_data) number_of_trailers = movie_data.get_number_of_movies() trailers_queue_size = movie_data.get_discovered_trailer_queue_size( ) if self.logger.isEnabledFor(LazyLogger.DISABLED): self.logger.debug_extra_verbose( source, 'size:', number_of_trailers, 'discoveredTrailersQueue size:', trailers_queue_size, 'readyToPlayQueue size:', playable_trailers.get_ready_to_play_queue().qsize(), 'trailersToFetchQueue size:', movie_data.get_trailers_to_fetch_queue_size()) projected_size = movie_data.get_projected_number_of_trailers() projected_sizes_map[source] = projected_size total_number_of_trailers += projected_size if not movie_data.is_discovery_complete( ) or number_of_trailers != 0: nothing_to_play = False # If we have played everything, then we start over. if (trailers_queue_size == 0 and playable_trailers.is_playable_trailers()): if self.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): self.logger.debug( 'Shuffling because discoveredTrailerQueue empty', trace=Trace.TRACE_DISCOVERY) movie_data.shuffle_discovered_trailers(mark_unplayed=True) if nothing_to_play: if self.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): self.logger.debug_verbose('Nothing to Play! numTrailers:', total_number_of_trailers) raise StopIteration # return None # Now, randomly pick playable_trailers to get a trailer from based upon # the number of trailers in each. # # We loop here because there may not be any trailers in the readyToPlayQueue # for a specific playable_trailers trailer = None attempts = 0 while trailer is None and attempts < 10: try: trailer_index_to_play = DiskUtils.RandomGenerator.randint( 0, total_number_of_trailers - 1) if self.logger.isEnabledFor(LazyLogger.DISABLED): self.logger.debug_extra_verbose( 'PlayableTrailerService.next trailer_index_to_play:', trailer_index_to_play) except (ValueError) as e: # Empty range Monitor.throw_exception_if_abort_requested(timeout=0.10) continue total_number_of_trailers = 0 found_playable_trailers = None for source in playable_trailers_map: playable_trailers = playable_trailers_map[source] if not playable_trailers.is_playable_trailers(): continue movie_data = playable_trailers.get_movie_data() self.throw_exception_on_forced_to_stop(movie_data=movie_data) projected_size = playable_trailers.get_projected_number_of_trailers( ) if self.logger.isEnabledFor(LazyLogger.DISABLED): self.logger.debug_extra_verbose('source:', source, 'projected size:', projected_size) total_number_of_trailers += projected_sizes_map[source] if self.logger.isEnabledFor(LazyLogger.DISABLED): self.logger.debug_extra_verbose( 'total_number_of_trailers:', total_number_of_trailers) if trailer_index_to_play < total_number_of_trailers: found_playable_trailers = playable_trailers break try: attempts += 1 if attempts > 1 and self.logger.isEnabledFor( LazyLogger.DEBUG_VERBOSE): self.logger.debug_verbose( 'PlayableTrailerService.next Attempt:', attempts, 'manager:', found_playable_trailers.__class__.__name__) trailer = found_playable_trailers.get_next_movie() TrailerCache.validate_cached_files(trailer) # If cached trailer is invalid, then skip over this trailer. if trailer[ Movie. DISCOVERY_STATE] != Movie.DISCOVERY_READY_TO_DISPLAY: trailer = None else: found_playable_trailers.set_starving(False) title = trailer[Movie.TITLE] + \ ' : ' + trailer[Movie.TRAILER] except queue.Empty: found_playable_trailers.set_starving(True) trailer = None duration_of_first_attempt = datetime.datetime.now() - start_time second_attempt_start_time = None second_method_attempts = None if trailer is None: if self.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): self.logger.debug_verbose( 'Trailer not found by preferred method', trace=Trace.TRACE) # Alternative method is to pick a random PlayableTrailersContainer to start # with and then find one that has a trailer. Otherwise, camp out. second_attempt_start_time = datetime.datetime.now() second_method_attempts = 0 iteration = 0 playable_trailers_list = [*playable_trailers_map.keys()] DiskUtils.RandomGenerator.shuffle(playable_trailers_list) for source in itertools.cycle(playable_trailers_list): try: playable_trailers = playable_trailers_map[source] movie_data = playable_trailers.get_movie_data() self.throw_exception_on_forced_to_stop( movie_data=movie_data) if (playable_trailers.get_number_of_playable_movies() == 0 and playable_trailers.get_movie_data( ).get_number_of_movies() > 0 and playable_trailers.is_playable_trailers()): if self.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): self.logger.debug_verbose( 'Shuffling because', 'discoveredTrailerQueue empty', 'source:', source, trace=Trace.TRACE_DISCOVERY) playable_trailers.get_movie_data().\ shuffle_discovered_trailers(mark_unplayed=True) trailer = playable_trailers.get_next_movie() # If cached trailer is invalid, then skip over this # trailer. if trailer[ Movie. DISCOVERY_STATE] != Movie.DISCOVERY_READY_TO_DISPLAY: trailer = None if trailer is not None: break except queue.Empty: pass # try again recently_played_trailers = \ PlayableTrailersContainer.get_recently_played_trailers() if len(recently_played_trailers) != 0: recently_played_list = list( recently_played_trailers.values()) trailer_index_to_play = DiskUtils.RandomGenerator.randint( 0, len(recently_played_trailers) - 1) trailer = recently_played_list[trailer_index_to_play] break iteration += 1 if iteration % len(playable_trailers_list) == 0: second_method_attempts += 1 Monitor.throw_exception_if_abort_requested(timeout=0.5) if trailer is None: self._next_failures += 1 else: trailer[Movie.TRAILER_PLAYED] = True title = trailer[Movie.TITLE] + ' : ' + trailer[Movie.TRAILER] duration = datetime.datetime.now() - start_time self._next_total_duration += duration.seconds self._next_calls += 1 self._next_attempts += attempts self._next_total_first_method_attempts += attempts Statistics.add_next_trailer_wait_time( duration_of_first_attempt.seconds, attempts) if second_method_attempts is not None: self._next_attempts += second_method_attempts self._next_second_attempts += second_method_attempts second_duration = datetime.datetime.now( ) - second_attempt_start_time self._next_second_total_Duration += second_duration.seconds Statistics.add_next_trailer_second_attempt_wait_time( second_duration.seconds, second_method_attempts) if trailer is None: raise StopIteration if self.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): self.logger.debug_verbose('Playing:', trailer[Movie.DETAIL_TITLE], trace=Trace.TRACE) Debug.validate_detailed_movie_properties(trailer) # Periodically report on played movie statistics self._played_movies_count += 1 if self.logger.is_trace_enabled(Trace.TRACE_PLAY_STATS): if (self._played_movies_count % 100) == 0: PlayStatistics.report_play_count_stats() return trailer
def run_worker(self): # type: () -> None """ Initial Discovery of all movies in Kodi. :return: """ # Discovery is done in two parts: # # 1- query DB for every movie in library # 2- Get additional information # # There are three types of trailers for these movies: # # a- Movies with local trailers # b- Movies with trailer URLS (typically youtube links from tmdb) # TMdb will need to be queried for details # c. Movies with no trailer information, requiring a check with tmdb # to see if one exists # # Because of the above, this manager will query the DB for every movie # and then only process the ones with local trailers. The others will # be handed off to their own managers. This is done because of # the way that this application works: # Once enough information to identify a movie that matches # what the user wants, it is added to the pool of movies that # can be randomly selected for playing. Once a movie has been # selected, it is placed into a TrailerFetcherQueue. A # TrailerFetcher then gathers the remaining information so that # it can be played. # # If the lion's share of movies in the pool require significant # extra processing because they don't have local trailers, then # the fetcher can get overwhelmed. clz = DiscoverLibraryMovies self._selected_keywords = [] self._excluded_keywords = [] self._selected_genres = [] self._excluded_genres = [] if Settings.get_filter_genres(): self._selected_genres = GenreUtils.get_internal_kodi_genre_ids( GenreUtils.LOCAL_DATABASE, exclude=False) self._excluded_genres = GenreUtils.get_internal_kodi_genre_ids( GenreUtils.LOCAL_DATABASE, exclude=True) self._selected_keywords = GenreUtils.get_internal_kodi_keyword_ids( GenreUtils.LOCAL_DATABASE, exclude=False) self._excluded_keywords = GenreUtils.get_internal_kodi_keyword_ids( GenreUtils.LOCAL_DATABASE, exclude=True) query = self.create_query( self._selected_genres, self._excluded_genres, self._selected_keywords, self._excluded_keywords) if Monitor.is_abort_requested(): return start_time = datetime.datetime.now() Monitor.throw_exception_if_abort_requested() # Expensive operation query_result = JsonUtilsBasic.get_kodi_json(query, dump_results=False) Monitor.throw_exception_if_abort_requested() elapsed_time = datetime.datetime.now() - start_time if clz.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): clz.logger.debug_verbose('Library query seconds:', elapsed_time.total_seconds()) movies_skipped = 0 movies_found = 0 movies_with_local_trailers = 0 movies_with_trailer_urls = 0 movies_without_trailer_info = 0 self.throw_exception_on_forced_to_stop() result = query_result.get('result', {}) del query_result movies = result.get('movies', []) del result DiskUtils.RandomGenerator.shuffle(movies) if self._libraryURLManager is None: self._libraryURLManager = DiscoverLibraryURLTrailerMovies() self._libraryNoTrailerInfoManager = DiscoverLibraryNoTrailerMovies() library_movies = [] library_url_movies = [] library_no_trailer_movies = [] empty_limit = 50 movie_data = None if Settings.is_enable_movie_stats(): movie_data = LibraryMovieStats() country_id = Settings.get_country_iso_3166_1().lower() certifications = WorldCertifications.get_certifications(country_id) unrated_id = certifications.get_unrated_certification().get_preferred_id() for movie in movies: self.throw_exception_on_forced_to_stop() try: #clz.logger.debug('movie:', movie) movies_found += 1 if Settings.get_hide_watched_movies() and Movie.LAST_PLAYED in movie: if (self.get_days_since_last_played(movie[Movie.LAST_PLAYED], movie[Movie.TITLE]) > Settings.get_minimum_days_since_watched()): movies_skipped += 1 if clz.logger.isEnabledFor(LazyLogger.DEBUG_EXTRA_VERBOSE): clz.logger.debug_extra_verbose(movie[Movie.TITLE], 'will not be played due to ' 'Hide', 'Watched Movies') continue # Normalize certification # if clz.logger.isEnabledFor(LazyLogger.DEBUG): # clz.logger.debug('mpaa:', movie[Movie.MPAA], # 'movie:', movie[Movie.TITLE]) if certifications.is_valid(movie.get(Movie.MPAA, '')): movie[Movie.MPAA] = unrated_id certification = certifications.get_certification( movie.get(Movie.MPAA), movie.get(Movie.ADULT)) movie[Movie.ADULT] = movie.get(Movie.ADULT, False) if not isinstance(movie[Movie.ADULT], bool): if clz.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): clz.logger.debug_verbose(movie[Movie.TITLE], 'has invalid ADULT field: ', movie[Movie.ADULT]) movie[Movie.ADULT] = str(movie[Movie.ADULT]).lower == 'true' movie[Movie.SOURCE] = Movie.LIBRARY_SOURCE movie.setdefault(Movie.TRAILER, '') movie[Movie.TYPE] = '' if clz.logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): Debug.validate_basic_movie_properties(movie) if Settings.is_enable_movie_stats(): movie_data.collect_data(movie) # Basic discovery is complete at this point. Now send # all of the movies without any trailer information to # DiscoverLibraryNoTrailerMovies while # those with trailer URLs to DiscoverLibraryURLTrailerMovies if certifications.filter(certification): trailer = movie[Movie.TRAILER] if trailer == '': movies_without_trailer_info += 1 library_no_trailer_movies.append(movie) elif trailer.startswith('plugin://') or trailer.startswith('http'): movies_with_trailer_urls += 1 library_url_movies.append(movie) elif Settings.get_include_library_trailers(): movies_with_local_trailers += 1 library_movies.append(movie) if len(library_movies) >= empty_limit: self.add_to_discovered_trailers(library_movies) del library_movies[:] # Unblock other discovery now that a few movies have been # found. if not self._some_movies_discovered_event.isSet(): self._some_movies_discovered_event.set() if len(library_no_trailer_movies) >= empty_limit: self._libraryNoTrailerInfoManager.add_to_discovered_trailers( library_no_trailer_movies) del library_no_trailer_movies[:] if len(library_url_movies) >= empty_limit: self._libraryURLManager.add_to_discovered_trailers( library_url_movies) del library_no_trailer_movies[:] # Unblock other discovery now that a few movies have been # found. self._some_movies_discovered_event.set() except AbortException: reraise(*sys.exc_info()) except Exception: clz.logger.exception('') try: if len(library_movies) >= 0: self.add_to_discovered_trailers(library_movies) if len(library_no_trailer_movies) >= 0: self._libraryNoTrailerInfoManager.add_to_discovered_trailers( library_no_trailer_movies) if len(library_url_movies) >= 0: self._libraryURLManager.add_to_discovered_trailers( library_url_movies) except AbortException: reraise(*sys.exc_info()) except Exception: clz.logger.exception('') if (clz.logger.isEnabledFor(LazyLogger.DEBUG) and clz.logger.is_trace_enabled(Trace.STATS)): clz.logger.debug('Local movies found in library:', movies_found, trace=Trace.STATS) clz.logger.debug('Local movies filtered out', movies_skipped, trace=Trace.STATS) clz.logger.debug('Movies with local trailers:', movies_with_local_trailers, trace=Trace.STATS) clz.logger.debug('Movies with trailer URLs:', movies_with_trailer_urls, trace=Trace.STATS) clz.logger.debug('Movies with no trailer information:', movies_without_trailer_info, trace=Trace.STATS) if Settings.is_enable_movie_stats(): movie_data.report_data() del movie_data