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
Ejemplo n.º 2
0
 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)
Ejemplo n.º 3
0
    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
Ejemplo n.º 6
0
    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