示例#1
0
    def __init__(self,
                 downloader: VideoDownloader,
                 url: str,
                 parse_json_as_youtube: bool = False) -> None:
        super().__init__(downloader,
                         url,
                         parse_json_as_youtube=parse_json_as_youtube)
        clz = type(self)
        if clz.logger is None:
            clz.logger = module_logger.getChild(clz.__name__)
            clz.country_id = Settings.get_country_iso_3166_1().lower()
            clz.certifications = WorldCertifications.get_certifications(
                clz.country_id)
            clz.unrated_id = clz.certifications.get_unrated_certification(
            ).get_preferred_id()

        self._trailer_info: List[MovieType] = []
        self.is_finished = False
    def validate_detailed_movie_properties(cls,
                                           movie: MovieType,
                                           stack_trace: bool = True,
                                           force_check: bool = False) -> bool:
        """
            Similar to validate_basic_movie_properties. Validates additional
            fields
        :param movie:
        :param stack_trace:
        :param force_check: Check even if debug level less than DEBUG_VERBOSE
        :return: True if no problems found
        """
        if not (cls._logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE)
                or force_check):
            return True

        details_properties = {
            Movie.WRITER:
            'default_' + Movie.WRITER,
            Movie.DETAIL_DIRECTORS:
            'default_' + Movie.DETAIL_DIRECTORS,
            Movie.DETAIL_TITLE:
            'default_' + Movie.TITLE,
            Movie.CAST:
            'default_' + Movie.CAST,
            Movie.PLOT:
            'default_' + Movie.PLOT,
            Movie.GENRE:
            'default_' + Movie.GENRE,
            Movie.STUDIO:
            'default_' + Movie.STUDIO,
            Movie.DETAIL_ACTORS:
            'default_' + Movie.ACTORS,
            Movie.DETAIL_GENRES:
            'default_' + Movie.GENRE,
            Movie.DETAIL_CERTIFICATION:
            'default_' + Movie.DETAIL_CERTIFICATION,
            Movie.DETAIL_CERTIFICATION_IMAGE:
            'default_' + Movie.DETAIL_CERTIFICATION_IMAGE,
            Movie.DETAIL_RUNTIME:
            'default_' + Movie.RUNTIME,
            Movie.DETAIL_WRITERS:
            'default_' + Movie.WRITER,
            # Movie.TMDB_TAGS: 'default_' + Movie.TAG,   # For TMDB
            Movie.DETAIL_STUDIOS:
            'default_' + Movie.STUDIO,
            Movie.RUNTIME:
            0,
            # Movie.ADULT,
            Movie.MPAA:
            'default_' + Movie.MPAA
        }

        cls.validate_basic_movie_properties(movie, stack_trace=stack_trace)
        failing_properties = []
        is_ok = True
        for property_name in details_properties.keys():
            if movie.get(property_name) is None:
                failing_properties.append(property_name)
                movie.setdefault(property_name,
                                 details_properties[property_name])
                is_ok = False

        if len(failing_properties) > 0:
            msg = ', '.join(failing_properties)
            if stack_trace:
                LazyLogger.dump_stack('Missing details property: ' + msg)
            else:
                cls._logger.debug_verbose('Missing properties:', msg)

        country_id = Settings.get_country_iso_3166_1().lower()
        certifications = WorldCertifications.get_certifications(country_id)
        if not certifications.is_valid(movie[Movie.MPAA]):
            if movie[Movie.MPAA] != '':
                cls._logger.debug_verbose(
                    f'Invalid certification: {movie[Movie.MPAA]} for movie: '
                    '{movie[Movie.TITLE]} set to NR')
            movie[Movie.MPAA] = certifications.get_unrated_certification() \
                .get_preferred_id()

        # assert is_ok, 'LEAK, Invalid property values'
        return is_ok
示例#3
0
def populate_youtube_movie_info(movie_data: MovieType, url: str) -> MovieType:
    """
        Creates a Kodi MovieType from the data returned from Youtube.
        Currently only used for TFH movies.

        Not used for iTunes movies. Rely on DiscoverItunesMovies for that.

        TFH trailers are titled: <reviewer> on <MOVIE_TITLE_ALL_CAPS>
        Here we can try to get just the movie title and then look up
        a likely match in TMDB (with date, and other info).
        TFH may not like us changing/guessing the movie title, however.
    """

    movie: Union[MovieType, None] = None
    dump_json = False
    missing_keywords = []
    try:
        trailer_id = movie_data.get('id')
        if trailer_id is None:
            missing_keywords.append('id')
            dump_json = True
        if movie_data.get('title') is None:
            missing_keywords.append('title')
            dump_json = True
        title = movie_data.get('title', 'missing title')
        # title_segments = title.split(' on ')
        # real_title_index = len(title_segments) - 1
        # movie_title = title_segments[real_title_index]
        movie_title = title
        trailer_url = 'https://youtu.be/' + trailer_id
        if movie_data.get('upload_date') is None:
            missing_keywords.append('upload_date')
            dump_json = True
            movie_data['upload_date'] = datetime.datetime.now().strftime(
                '%Y%m%d')
        upload_date = movie_data.get('upload_date', '19000101')  # 20120910
        year = upload_date[0:4]
        year = int(year)
        if movie_data.get('thumbnail') is None:
            missing_keywords.append('thumbnail')
            dump_json = True
        thumbnail = movie_data.get('thumbnail', '')

        original_language = ''
        if movie_data.get('description') is None:
            missing_keywords.append('description')
            dump_json = True
        description = movie_data.get('description', '')
        country_id = Settings.get_country_iso_3166_1().lower()
        certifications = WorldCertifications.get_certifications(country_id)
        unrated_id = certifications.get_unrated_certification(
        ).get_preferred_id()
        trailers_in_playlist = movie_data.get('n_entries', 1)
        playlist_index = movie_data.get('playlist_index', 0)
        # Tags might have some good stuff, but very unorganized and full of junk
        # tags: Dict[str, str] = movie_data.get('tags', {})
        if movie_data.get('average_rating') is None:
            missing_keywords.append('average_rating')
            dump_json = True
        if movie_data.get('duration') is None:
            missing_keywords.append('duration')
            dump_json = True
        movie = {
            Movie.SOURCE:
            'unknown',
            Movie.YOUTUBE_ID:
            trailer_id,
            Movie.TITLE:
            movie_title,
            Movie.YEAR:
            year,
            Movie.ORIGINAL_LANGUAGE:
            original_language,
            Movie.TRAILER:
            trailer_url,
            Movie.PLOT:
            description,
            Movie.THUMBNAIL:
            thumbnail,
            Movie.DISCOVERY_STATE:
            Movie.NOT_FULLY_DISCOVERED,
            Movie.MPAA:
            unrated_id,
            Movie.ADULT:
            False,
            Movie.RATING:
            movie_data.get('average_rating', 0.0),
            # Kodi measures in seconds
            Movie.RUNTIME:
            movie_data.get('duration', 1.0) * 60
        }
        if playlist_index is not None:
            movie[Movie.YOUTUBE_PLAYLIST_INDEX] = playlist_index
            movie[Movie.YOUTUBE_TRAILERS_IN_PLAYLIST] = trailers_in_playlist
    except Exception as e:
        dump_json = True
        module_logger.exception(e)
    if dump_json:
        if module_logger.isEnabledFor(LazyLogger.DEBUG):
            module_logger.debug(
                'Missing json data. Missing keywords:',
                ', '.join(missing_keywords), 'URL:', url, '\njson:',
                json.dumps(movie_data,
                           encoding='utf-8',
                           ensure_ascii=False,
                           indent=3,
                           sort_keys=True))
    return movie
示例#4
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
示例#5
0
    def populate_youtube_movie_info(cls, movie_data: MovieType,
                                    url: str) -> MovieType:
        """
            Creates a Kodi MovieType from the data returned from Youtube.

            Not used for iTunes movies. Rely on DiscoverItunesMovies for that.

            TFH trailers are titled:

             Formats: Reviewer on CAPS TITLE (most common)
                      Reviewer talks TITLE
                      Reviewer talks about TITLE
                      Reviewer discusses TITLE
                      Reviewer's TITLE
                      TITLE
                      Reviewer In Conversation With Person
                      Reviewer covers TITLE
                      Reviewer introduces TITLE for the Cinenasty series

            Here we can try to get just the movie title and then look up
            a likely match in TMDB (with date, and other info).
            TFH may not like us changing/guessing the movie title, however.
        """
        movie: Union[MovieType, None] = None
        dump_json = False
        missing_keywords = []
        try:
            trailer_id = movie_data.get('id')
            if trailer_id is None:
                missing_keywords.append('id')
                dump_json = True
            url = movie_data.get('url')
            if movie_data.get('title') is None:
                missing_keywords.append('title')
                dump_json = True

            movie_title = movie_data.get('title', 'Missing Title')
            trailer_url = 'https://youtu.be/' + trailer_id

            upload_date = movie_data.get('upload_date', '19000101')
            year_str = upload_date[0:4]

            year = int(year_str)
            if movie_data.get('thumbnail') is not None:
                thumbnail = movie_data.get('thumbnail')

            if movie_data.get('description') is None:
                description = movie_data.get('description', '')
            country_id = Settings.get_country_iso_3166_1().lower()
            certifications = WorldCertifications.get_certifications(country_id)
            unrated_id = certifications.get_unrated_certification(
            ).get_preferred_id()
            trailers_in_playlist = movie_data.get('n_entries', 1)
            movie = {
                Movie.SOURCE:
                'unknown',
                Movie.YOUTUBE_ID:
                trailer_id,
                Movie.TITLE:
                movie_title,
                Movie.YEAR:
                0,
                Movie.ORIGINAL_LANGUAGE:
                '',
                Movie.TRAILER:
                trailer_url,
                Movie.PLOT:
                '',
                Movie.THUMBNAIL:
                '',
                Movie.DISCOVERY_STATE:
                Movie.NOT_FULLY_DISCOVERED,
                Movie.MPAA:
                unrated_id,
                Movie.ADULT:
                False,
                Movie.RATING:
                movie_data.get('average_rating', 0.0),
                # Kodi measures in seconds
                # At least for TFH, this appears to be time of trailer
                # (not movie), measured in 1/60 of a
                # second, or 60Hz frames. Weird.
                Movie.RUNTIME:
                0  # Ignore trailer length
            }
        except Exception as e:
            dump_json = True
            cls.logger.exception(f'url: {url}')
        if dump_json:
            if cls.logger.isEnabledFor(LazyLogger.DEBUG):
                cls.logger.debug(
                    'Missing json data. Missing keywords:',
                    ', '.join(missing_keywords), 'URL:', url, '\njson:',
                    json.dumps(movie_data,
                               ensure_ascii=False,
                               indent=3,
                               sort_keys=True))
        return movie
示例#6
0
    def populate_youtube_movie_info(cls, movie_data: MovieType,
                                    url: str) -> MovieType:
        """
            Creates a Kodi MovieType from the data returned from Youtube.

        """
        movie: Union[MovieType, None] = None
        dump_json = False
        missing_keywords = []
        try:
            trailer_id = movie_data.get('id')
            if trailer_id is None:
                missing_keywords.append('id')
                dump_json = True
            if movie_data.get('title') is None:
                missing_keywords.append('title')
                dump_json = True

            movie_title = movie_data.get('title', 'Missing Title')
            trailer_url = 'https://youtu.be/' + trailer_id
            if movie_data.get('upload_date') is None:
                missing_keywords.append('upload_date')
                dump_json = True
                movie_data['upload_date'] = datetime.datetime.now().strftime(
                    '%Y%m%d')
            upload_date = movie_data.get('upload_date', '19000101')  # 20120910
            year_str = upload_date[0:4]

            year = int(year_str)
            if movie_data.get('thumbnail') is None:
                missing_keywords.append('thumbnail')
                dump_json = True
            thumbnail = movie_data.get('thumbnail', '')

            if movie_data.get('description') is None:
                missing_keywords.append('description')
                dump_json = True
            description = movie_data.get('description', '')
            country_id = Settings.get_country_iso_3166_1().lower()
            certifications = WorldCertifications.get_certifications(country_id)
            unrated_id = certifications.get_unrated_certification(
            ).get_preferred_id()

            # Tags might have some good stuff, but very unorganized and full of junk
            # tags: Dict[str, str] = movie_data.get('tags', {})
            if movie_data.get('average_rating') is None:
                missing_keywords.append('average_rating')
                dump_json = True
            if movie_data.get('duration') is None:
                missing_keywords.append('duration')
                dump_json = True
            movie = {
                Movie.SOURCE:
                'unknown',
                Movie.YOUTUBE_ID:
                trailer_id,
                Movie.TITLE:
                movie_title,
                Movie.YEAR:
                year,
                Movie.ORIGINAL_LANGUAGE:
                '',
                Movie.TRAILER:
                trailer_url,
                Movie.PLOT:
                description,
                Movie.THUMBNAIL:
                thumbnail,
                Movie.DISCOVERY_STATE:
                Movie.NOT_FULLY_DISCOVERED,
                Movie.MPAA:
                unrated_id,
                Movie.ADULT:
                False,
                Movie.RATING:
                movie_data.get('average_rating', 0.0),
                # Kodi measures in seconds
                # At least for TFH, this appears to be time of trailer
                # (not movie), measured in 1/60 of a
                # second, or 60Hz frames. Weird.
                Movie.RUNTIME:
                0  # Ignore trailer length
            }
        except Exception as e:
            dump_json = True
            cls.logger.exception(f'url: {url}')
        if dump_json:
            if cls.logger.isEnabledFor(LazyLogger.DEBUG):
                cls.logger.debug(
                    'Missing json data. Missing keywords:',
                    ', '.join(missing_keywords), 'URL:', url, '\njson:',
                    json.dumps(movie_data,
                               ensure_ascii=False,
                               indent=3,
                               sort_keys=True))
        return movie
    def __init__(self, title: str, year: Union[str, None],
                 runtime_seconds: int) -> None:
        clz = type(self)
        if clz._logger is None:
            clz._logger = module_logger.getChild(clz.__name__)

        self._title_to_match: str = title
        self._year_to_match: Union[str, None] = year
        self._runtime_seconds_to_match: int = runtime_seconds

        self.candidate_movies: List[clz.CandidateMovie] = []

        data = {
            'api_key': Settings.get_tmdb_api_key(),
            'page': '1',
            'query': title,
            'language': Settings.get_lang_iso_639_1()
        }

        if year is not None:
            data['primary_release_year'] = year

        try:
            include_adult = 'false'

            country_id = Settings.get_country_iso_3166_1().lower()
            certifications = WorldCertifications.get_certifications(country_id)
            adult_certification = certifications.get_certification(
                'dummy', True)
            if certifications.filter(adult_certification):
                include_adult = 'true'
            data['include_adult'] = include_adult
            data['append_to_response'] = 'alternative_titles'

            url = 'https://api.themoviedb.org/3/search/movie'
            status_code, _info_string = \
                JsonUtilsBasic.get_json(url, params=data,
                                        dump_msg='get_tmdb_id_from_title_year',
                                        dump_results=True,
                                        error_msg=title +
                                        f' ({year})')
            if clz._logger.isEnabledFor(LazyLogger.DEBUG_EXTRA_VERBOSE):
                clz._logger.debug_extra_verbose(
                    f'Getting TMDB movie for title: {title} year: {year} '
                    f'runtime: {runtime_seconds}')

            if _info_string is not None:
                results = _info_string.get('results', [])
                if len(results) > 1:
                    if clz._logger.isEnabledFor(
                            LazyLogger.DEBUG_EXTRA_VERBOSE):
                        clz._logger.debug_extra_verbose(
                            f'Got multiple matching movies: {title} '
                            f'year: {year} runtime: {runtime_seconds}')

                # TODO: Improve. Create best trailer function from get_tmdb_trailer
                # TODO: find best trailer_id

                current_language = Settings.get_lang_iso_639_1()

                for movie in results:
                    release_date = movie.get('release_date', '')  # 1932-04-22
                    tmdb_year = release_date[:-6]
                    movie[Movie.YEAR] = tmdb_year
                    tmdb_title = movie.get('title', '')
                    tmdb_id = movie.get('id', None)
                    tmdb_language = movie.get('original_language')
                    runtime_minutes = movie.get(Movie.RUNTIME, 0)
                    runtime_seconds = int(runtime_minutes * 60)

                    titles = movie.get('alternative_titles', {'titles': []})
                    alt_titles = []
                    for title in titles['titles']:
                        alt_title = (title['title'], title['iso_3166_1'])
                        alt_titles.append(alt_title)

                    if clz._logger.isEnabledFor(
                            LazyLogger.DEBUG_EXTRA_VERBOSE):
                        clz._logger.debug_extra_verbose(
                            f'Matching Movie date: {tmdb_year}'
                            f' tmdb_title: {tmdb_title}'
                            f' lang: {tmdb_language}'
                            f' current_lang: {current_language}')

                    self._add(movie, tmdb_title, tmdb_year, tmdb_language,
                              tmdb_id, runtime_seconds)

        except AbortException:
            reraise(*sys.exc_info())

        except Exception as e:
            clz._logger.exception(e)
    def _get_tmdb_id_from_title_year(title: str, year: int) -> Optional[int]:
        """
            When we don't have a trailer for a movie, we can
            see if TMDB has one.
        :param title:
        :param year:
        :return:
        """
        year_str = str(year)
        found_movie = None
        trailer_id = None
        data = {}
        data['api_key'] = Settings.get_tmdb_api_key()
        data['page'] = '1'
        data['query'] = title
        data['language'] = Settings.get_lang_iso_639_1()
        data['primary_release_year'] = year

        try:
            include_adult = 'false'

            country_id = Settings.get_country_iso_3166_1().lower()
            certifications = WorldCertifications.get_certifications(country_id)
            adult_certification = certifications.get_certification(
                'dummy', True)
            if certifications.filter(adult_certification):
                include_adult = 'true'
            data['include_adult'] = include_adult

            url = 'https://api.themoviedb.org/3/search/movie'
            status_code, _info_string = \
                JsonUtilsBasic.get_json(url, params=data,
                                        dump_msg='get_tmdb_id_from_title_year',
                                        dump_results=True,
                                        error_msg=title +
                                                  ' (' + year_str + ')')
            if _info_string is not None:
                results = _info_string.get('results', [])
                if len(results) > 1:
                    if TMDBUtils._logger.isEnabledFor(
                            LazyLogger.DEBUG_EXTRA_VERBOSE):
                        TMDBUtils._logger.debug_extra_verbose(
                            'Got multiple matching movies:', title, 'year:',
                            year)

                # TODO: Improve. Create best trailer function from get_tmdb_trailer
                # TODO: find best trailer_id

                matches = []
                current_language = Settings.get_lang_iso_639_1()
                movie = None
                for movie in results:
                    release_date = movie.get('release_date', '')  # 1932-04-22
                    found_year = release_date[:-6]
                    found_title = movie.get('title', '')

                    if (found_title.lower() == title.lower()
                            and found_year == year_str
                            and movie.get('original_language')
                            == current_language):
                        matches.append(movie)

                # TODO: Consider close match heuristics.

                if len(matches) == 1:
                    found_movie = matches[0]
                elif len(matches) > 1:
                    if TMDBUtils._logger.isEnabledFor(
                            LazyLogger.DEBUG_VERBOSE):
                        TMDBUtils._logger.debug_extra_verbose(
                            'More than one matching movie in same year choosing first '
                            'one matching current language.', 'Num choices:',
                            len(matches))
                    found_movie = matches[0]

                if movie is None:
                    if TMDBUtils._logger.isEnabledFor(
                            LazyLogger.DEBUG_EXTRA_VERBOSE):
                        TMDBUtils._logger.debug_extra_verbose(
                            'Could not find movie:', title, 'year:', year,
                            'at TMDB. found', len(results), 'candidates')
                    for a_movie in results:
                        release_date = a_movie.get('release_date',
                                                   '')  # 1932-04-22
                        found_year = release_date[:-6]
                        found_title = a_movie.get('title', '')
                        tmdb_id = a_movie.get('id', None)
                        if TMDBUtils._logger.isEnabledFor(LazyLogger.DISABLED):
                            TMDBUtils._logger.debug_extra_verbose(
                                'found:', found_title, '(', found_year, ')',
                                'tmdb id:', tmdb_id)
                        tmdb_data = MovieEntryUtils.get_alternate_titles(
                            title, tmdb_id)
                        for alt_title, country in tmdb_data['alt_titles']:
                            if alt_title.lower() == title.lower():
                                found_movie = tmdb_data  # Not actually in "movie" format
                                break
            else:
                if TMDBUtils._logger.isEnabledFor(LazyLogger.INFO):
                    TMDBUtils._logger.info('Could not find movie:', title,
                                           'year:', year,
                                           'at TMDB. found no candidates')
        except AbortException:
            reraise(*sys.exc_info())

        except Exception:
            TMDBUtils._logger.exception('')

        tmdb_id = None
        if found_movie is not None:
            tmdb_id = found_movie.get('id', None)
        if tmdb_id is None:
            return None
        return int(tmdb_id)