Exemple #1
0
def tvdb_search_series(
    token: str,
    series: Optional[str] = None,
    id_imdb: Optional[str] = None,
    id_zap2it: Optional[str] = None,
    language: Optional[Language] = None,
    cache: bool = True,
) -> dict:
    """
    Allows the user to search for a series based on the following parameters.

    Online docs: https://api.thetvdb.com/swagger#!/Search/get_search_series
    Note: results a maximum of 100 entries per page, no option for pagination.
    """
    Language.ensure_valid_for_tvdb(language)
    url = "https://api.thetvdb.com/search/series"
    parameters = {"name": series, "imdbId": id_imdb, "zap2itId": id_zap2it}
    headers = {"Authorization": f"Bearer {token}"}
    if language:
        headers["Accept-Language"] = language.a2
    status, content = request_json(
        url, parameters, headers=headers, cache=cache is True and language is None
    )
    if status == 401:
        raise MnamerException("invalid token")
    elif status == 405:
        raise MnamerException(
            "series, id_imdb, id_zap2it parameters are mutually exclusive"
        )
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not content.get("data"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content
Exemple #2
0
def tvdb_series_id_episodes_query(
    token: str,
    id_tvdb: Union[str, int],
    episode: int = None,
    season: int = None,
    page: int = 1,
    lang: str = "en",
    cache: bool = True,
) -> dict:
    """
    Allows the user to query against episodes for the given series.

    Note: Paginated with 100 results per page; omitted imdbId-- when would you
    ever need to query against both tvdb and imdb series ids?
    Online docs: api.thetvdb.com/swagger#!/Series/get_series_id_episodes_query.
    """
    if lang not in TVDB_LANGUAGE_CODES:
        raise MnamerException("'lang' must be one of %s" %
                              ",".join(TVDB_LANGUAGE_CODES))
    url = f"https://api.thetvdb.com/series/{id_tvdb}/episodes/query"
    headers = {"Accept-Language": lang, "Authorization": f"Bearer {token}"}
    parameters = {"airedSeason": season, "airedEpisode": episode, "page": page}
    status, content = request_json(url,
                                   parameters,
                                   headers=headers,
                                   cache=cache)
    if status == 401:
        raise MnamerException("invalid token")
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not content.get("data"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content
Exemple #3
0
def tvdb_search_series(
    token: str,
    series: str = None,
    id_imdb: str = None,
    id_zap2it: str = None,
    lang: str = "en",
    cache: bool = True,
) -> dict:
    """
    Allows the user to search for a series based on the following parameters.

    Online docs: https://api.thetvdb.com/swagger#!/Search/get_search_series
    Note: results a maximum of 100 entries per page, no option for pagination.
    """
    if lang not in TVDB_LANGUAGE_CODES:
        raise MnamerException("'lang' must be one of %s" %
                              ",".join(TVDB_LANGUAGE_CODES))
    url = "https://api.thetvdb.com/search/series"
    parameters = {"name": series, "imdbId": id_imdb, "zap2itId": id_zap2it}
    headers = {"Accept-Language": lang, "Authorization": f"Bearer {token}"}
    status, content = request_json(url,
                                   parameters,
                                   headers=headers,
                                   cache=cache)
    if status == 401:
        raise MnamerException("invalid token")
    elif status == 405:
        raise MnamerException(
            "series, id_imdb, id_zap2it parameters are mutually exclusive")
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not content.get("data"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content
Exemple #4
0
def tvdb_series_id_episodes(
    token: str,
    id_tvdb: Union[str, int],
    page: int = 1,
    lang: str = "en",
    cache: bool = True,
) -> dict:
    """
    All episodes for a given series.

    Note: Paginated with 100 results per page.
    Online docs: api.thetvdb.com/swagger#!/Series/get_series_id_episodes.
    """
    if lang not in TVDB_LANGUAGE_CODES:
        raise MnamerException("'lang' must be one of %s" %
                              ",".join(TVDB_LANGUAGE_CODES))
    url = f"https://api.thetvdb.com/series/{id_tvdb}/episodes"
    headers = {"Accept-Language": lang, "Authorization": f"Bearer {token}"}
    parameters = {"page": page}
    status, content = request_json(url,
                                   parameters,
                                   headers=headers,
                                   cache=cache)
    if status == 401:
        raise MnamerException("invalid token")
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not content.get("data"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content
Exemple #5
0
def omdb_search(
    api_key: str,
    query: str,
    year: Optional[int] = None,
    media: Optional[str] = None,
    page: int = 1,
    cache: bool = True,
) -> dict:
    """
    Search for media using the Open Movie Database.

    Online docs: http://www.omdbapi.com/#parameters.
    """
    if page < 1 or page > 100:
        raise MnamerException("page must be between 1 and 100")
    url = "http://www.omdbapi.com"
    parameters = {
        "apikey": api_key,
        "s": query,
        "y": year,
        "type": media,
        "page": page,
    }
    parameters = clean_dict(parameters)
    status, content = request_json(url, parameters, cache=cache)
    if status == 401:
        raise MnamerException("invalid API key")
    elif content and not content.get("totalResults"):
        raise MnamerNotFoundException()
    elif not content or status != 200:  # pragma: no cover
        raise MnamerNetworkException("OMDb down or unavailable?")
    return content
Exemple #6
0
 def parse_args(self, args=None, namespace=None):
     """
     Overrides ArgumentParser's parse_args to raise an MnamerException on
     error which can be caught in the main program loop instead of just
     exiting immediately.
     """
     load_arguments, unknowns = self.parse_known_args(args, namespace)
     if unknowns:
         raise MnamerException(f"invalid arguments: {','.join(unknowns)}")
     if not vars(load_arguments):
         raise MnamerException(self.usage)
     return load_arguments
Exemple #7
0
def tvdb_series_id_episodes(
    token: str,
    id_tvdb: Union[str, int],
    page: int = 1,
    language: Optional[Language] = None,
    cache: bool = True,
) -> dict:
    """
    All episodes for a given series.

    Note: Paginated with 100 results per page.
    Online docs: api.thetvdb.com/swagger#!/Series/get_series_id_episodes.
    """
    Language.ensure_valid_for_tvdb(language)
    url = f"https://api.thetvdb.com/series/{id_tvdb}/episodes"
    headers = {"Authorization": f"Bearer {token}"}
    if language:
        headers["Accept-Language"] = language.a2
    parameters = {"page": page}
    status, content = request_json(
        url,
        parameters,
        headers=headers,
        cache=cache is True and language is None,
    )
    if status == 401:
        raise MnamerException("invalid token")
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not content.get("data"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content
Exemple #8
0
def tmdb_search_movies(
    api_key: str,
    title: str,
    year: Optional[Union[int, str]] = None,
    language: Optional[Language] = None,
    region: Optional[str] = None,
    adult: bool = False,
    page: int = 1,
    cache: bool = True,
) -> dict:
    """
    Search for movies using The Movie Database.

    Online docs: developers.themoviedb.org/3/search/search-movies.
    """
    url = "https://api.themoviedb.org/3/search/movie"
    parameters = {
        "api_key": api_key,
        "query": title,
        "page": page,
        "include_adult": adult,
        "language": language,
        "region": region,
        "year": year,
    }
    status, content = request_json(url, parameters, cache=cache)
    if status == 401:
        raise MnamerException("invalid API key")
    elif status != 200 or not any(content.keys()):  # pragma: no cover
        raise MnamerNetworkException("TMDb down or unavailable?")
    elif status == 404 or status == 422 or not content.get("total_results"):
        raise MnamerNotFoundException
    return content
Exemple #9
0
def tvmaze_show_lookup(
    id_imdb: Optional[str] = None,
    id_tvdb: Union[str, int] = None,
    cache: bool = True,
    attempt: int = 1,
) -> dict:
    """
    If you already know a show's tvrage, thetvdb or IMDB ID, you can use this
    endpoint to find this exact show on TVmaze.

    Online docs: https://www.tvmaze.com/api#show-lookup
    """
    if not [id_imdb, id_tvdb].count(None) == 1:
        raise MnamerException("id_imdb and id_tvdb are mutually exclusive")
    url = "http://api.tvmaze.com/lookup/shows"
    parameters = {"imdb": id_imdb, "thetvdb": id_tvdb}
    status, content = request_json(url, parameters, cache=cache)
    if status == 443 and attempt <= MAX_RETRIES:  # pragma: no cover
        sleep(attempt * 2)
        return tvmaze_show_lookup(id_imdb, id_tvdb, cache, attempt + 1)
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not content:  # pragma: no cover
        raise MnamerNetworkException
    return content
Exemple #10
0
 def ensure_valid_for_tvdb(language: Optional["Language"]):
     valid = {
         "cs",
         "da",
         "de",
         "el",
         "en",
         "es",
         "fi",
         "fr",
         "he",
         "hr",
         "hu",
         "it",
         "ja",
         "ko",
         "nl",
         "no",
         "pl",
         "pt",
         "ru",
         "sl",
         "sv",
         "tr",
         "zh",
     }
     if language is not None and language.a2 not in valid:
         raise MnamerException("'lang' must be one of %s" % ",".join(valid))
Exemple #11
0
def tvdb_series_id(
    token: str,
    id_tvdb: Union[str, int],
    language: Optional[Language] = None,
    cache: bool = True,
) -> dict:
    """
    Returns a series records that contains all information known about a
    particular series id.

    Online docs: api.thetvdb.com/swagger#!/Series/get_series_id.
    """
    Language.ensure_valid_for_tvdb(language)
    url = f"https://api.thetvdb.com/series/{id_tvdb}"
    headers = {"Authorization": f"Bearer {token}"}
    if language:
        headers["Accept-Language"] = language.a2
    status, content = request_json(
        url, headers=headers, cache=cache is True and language is None
    )
    if status == 401:
        raise MnamerException("invalid token")
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not content.get("data"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    elif content["data"]["id"] == 0:
        raise MnamerNotFoundException
    return content
Exemple #12
0
def tvdb_series_id_episodes_query(
    token: str,
    id_tvdb: str,
    episode: Optional[int] = None,
    season: Optional[int] = None,
    page: int = 1,
    language: Optional[Language] = None,
    cache: bool = True,
) -> dict:
    """
    Allows the user to query against episodes for the given series.

    Note: Paginated with 100 results per page; omitted imdbId-- when would you
    ever need to query against both tvdb and imdb series ids?
    Online docs: api.thetvdb.com/swagger#!/Series/get_series_id_episodes_query.
    """
    Language.ensure_valid_for_tvdb(language)
    url = f"https://api.thetvdb.com/series/{id_tvdb}/episodes/query"
    headers = {"Authorization": f"Bearer {token}"}
    if language:
        headers["Accept-Language"] = language.a2
    parameters = {"airedSeason": season, "airedEpisode": episode, "page": page}
    status, content = request_json(
        url,
        parameters,
        headers=headers,
        cache=cache is True and language is None,
    )
    if status == 401:
        raise MnamerException("invalid token")
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not content.get("data"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content
Exemple #13
0
 def _load_configuration(self, path: Union[Path, str]):
     path = Template(str(path)).substitute(environ)
     with open(path, mode="r") as file_pointer:
         data = json.loads(file_pointer.read())
     for key in data:
         if key not in self._attribute_metadata():
             raise MnamerException(f"invalid setting: {key}")
     self._config_data = data
Exemple #14
0
def omdb_title(
    api_key: str,
    id_imdb: Optional[str] = None,
    media: Optional[str] = None,
    title: Optional[str] = None,
    season: Optional[int] = None,
    episode: Optional[int] = None,
    year: Optional[int] = None,
    plot: Optional[str] = None,
    cache: bool = True,
) -> dict:
    """
    Looks up media by id using the Open Movie Database.

    Online docs: http://www.omdbapi.com/#parameters
    """
    if (not title and not id_imdb) or (title and id_imdb):
        raise MnamerException("either id_imdb or title must be specified")
    elif plot and plot not in OMDB_PLOT_TYPES:
        raise MnamerException("plot must be one of %s" %
                              ",".join(OMDB_PLOT_TYPES))
    url = "http://www.omdbapi.com"
    parameters = {
        "apikey": api_key,
        "i": id_imdb,
        "t": title,
        "y": year,
        "season": season,
        "episode": episode,
        "type": media,
        "plot": plot,
    }
    parameters = clean_dict(parameters)
    status, content = request_json(url, parameters, cache=cache)
    error = content.get("Error") if isinstance(content, dict) else None
    if status == 401:
        if error == 'Request limit reached!':
            raise MnamerException("API request limit reached")
        raise MnamerException("invalid API key")
    elif status != 200 or not isinstance(content, dict):  # pragma: no cover
        raise MnamerNetworkException("OMDb down or unavailable?")
    elif error:
        raise MnamerNotFoundException(error)
    return content
Exemple #15
0
def tmdb_find(
    api_key: str,
    external_source: str,
    external_id: str,
    language: Optional[Language] = None,
    cache: bool = True,
) -> dict:
    """
    Search for The Movie Database objects using another DB's foreign key.

    Note: language codes aren't checked on this end or by TMDb, so if you
        enter an invalid language code your search itself will succeed, but
        certain fields like synopsis will just be empty.

    Online docs: developers.themoviedb.org/3/find.
    """
    sources = [
        "imdb_id", "freebase_mid", "freebase_id", "tvdb_id", "tvrage_id"
    ]
    if external_source not in sources:
        raise MnamerException(f"external_source must be in {sources}")
    if external_source == "imdb_id" and not match(r"tt\d+", external_id):
        raise MnamerException("invalid imdb tt-const value")
    url = "https://api.themoviedb.org/3/find/" + external_id or ""
    parameters = {
        "api_key": api_key,
        "external_source": external_source,
        "language": language,
    }
    keys = [
        "movie_results",
        "person_results",
        "tv_episode_results",
        "tv_results",
        "tv_season_results",
    ]
    status, content = request_json(url, parameters, cache=cache)
    if status == 401:
        raise MnamerException("invalid API key")
    elif status != 200 or not any(content.keys()):  # pragma: no cover
        raise MnamerNetworkException("TMDb down or unavailable?")
    elif status == 404 or not any(content.get(k, {}) for k in keys):
        raise MnamerNotFoundException
    return content
Exemple #16
0
 def parse(cls, value: Any) -> Optional["Language"]:
     if not value:
         return None
     if isinstance(value, cls):
         return value
     if isinstance(value, dict):
         return cls(*value.values())
     if isinstance(value, tuple):
         return cls(*value)
     try:
         if getattr(value, "alpha3", None):
             return cls(value.name, value.alpha2, value.alpha3)
     except:
         raise MnamerException("Could not determine language")
     value = value.lower()
     for row in _KNOWN:
         for item in row:
             if value == item:
                 return cls(row[0].capitalize(), row[1], row[2])
     raise MnamerException("Could not determine language")
Exemple #17
0
 def load(self):
     arg_loader = ArgLoader(*self.specifications())
     try:
         arguments = arg_loader.load()
     except RuntimeError as e:
         raise MnamerException(e)
     config_path = arguments.get("config_path", crawl_out(".mnamer-v2.json"))
     config = json_loads(str(config_path)) if config_path else {}
     if not self.config_ignore and not arguments.get("config_ignore"):
         self.bulk_apply(config)
     if arguments:
         self.bulk_apply(arguments)
Exemple #18
0
def tvdb_series_id(token: str,
                   id_tvdb: Union[str, int],
                   lang: str = "en",
                   cache: bool = True) -> dict:
    """
    Returns a series records that contains all information known about a
    particular series id.

    Online docs: api.thetvdb.com/swagger#!/Series/get_series_id.
    """
    if lang not in TVDB_LANGUAGE_CODES:
        raise MnamerException("'lang' must be one of %s" %
                              ",".join(TVDB_LANGUAGE_CODES))
    url = f"https://api.thetvdb.com/series/{id_tvdb}"
    headers = {"Accept-Language": lang, "Authorization": f"Bearer {token}"}
    status, content = request_json(url, headers=headers, cache=cache)
    if status == 401:
        raise MnamerException("invalid token")
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not content.get("data"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content
Exemple #19
0
def tvdb_refresh_token(token: str) -> str:
    """
    Refreshes JWT token.

    Online docs: api.thetvdb.com/swagger#!/Authentication/get_refresh_token.
    """
    url = "https://api.thetvdb.com/refresh_token"
    headers = {"Authorization": f"Bearer {token}"}
    status, content = request_json(url, headers=headers, cache=False)
    if status == 401:
        raise MnamerException("invalid token")
    elif status != 200 or not content.get("token"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content["token"]
Exemple #20
0
def tvdb_episodes_id(token: str,
                     id_tvdb: Union[str, int],
                     lang: str = "en",
                     cache: bool = True) -> dict:
    """
    Returns the full information for a given episode id.

    Online docs: https://api.thetvdb.com/swagger#!/Episodes.
    """
    if lang not in TVDB_LANGUAGE_CODES:
        raise MnamerException("'lang' must be one of %s" %
                              ",".join(TVDB_LANGUAGE_CODES))
    url = f"https://api.thetvdb.com/episodes/{id_tvdb}"
    headers = {"Accept-Language": lang, "Authorization": f"Bearer {token}"}
    status, content = request_json(url, headers=headers, cache=cache)
    if status == 401:
        raise MnamerException("invalid token")
    elif status == 404:
        raise MnamerNotFoundException
    elif status == 200 and "invalidLanguage" in content.get("errors", {}):
        raise MnamerNotFoundException
    elif status != 200 or not content.get("data"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content
Exemple #21
0
def tmdb_movies(
    api_key: str,
    id_tmdb: Union[int, str],
    language: str = "en-US",
    cache: bool = True,
) -> dict:
    """
    Lookup a movie item using The Movie Database.

    Online docs: developers.themoviedb.org/3/movies.
    """
    try:
        url = f"https://api.themoviedb.org/3/movie/{id_tmdb}"
    except ValueError:
        raise MnamerException("id_tmdb must be numeric")
    parameters = {"api_key": api_key, "language": language}
    status, content = request_json(url, parameters, cache=cache)
    if status == 401:
        raise MnamerException("invalid API key")
    elif status == 404:
        raise MnamerNotFoundException
    elif status != 200 or not any(content.keys()):  # pragma: no cover
        raise MnamerNetworkException("TMDb down or unavailable?")
    return content
Exemple #22
0
def tvdb_login(api_key: str) -> str:
    """
    Logs into TVDb using the provided api key.

    Note: You can register for a free TVDb key at thetvdb.com/?tab=apiregister
    Online docs: api.thetvdb.com/swagger#!/Authentication/post_login.
    """
    url = "https://api.thetvdb.com/login"
    body = {"apikey": api_key}
    status, content = request_json(url, body=body, cache=False)
    if status == 401:
        raise MnamerException("invalid api key")
    elif status != 200 or not content.get("token"):  # pragma: no cover
        raise MnamerNetworkException("TVDb down or unavailable?")
    return content["token"]
Exemple #23
0
def provider_search(metadata, id_key=None, **options):
    """ An adapter for mapi's Provider classes
    """
    media = metadata["media"]
    if not hasattr(provider_search, "providers"):
        provider_search.providers = {}
    if media not in provider_search.providers:
        api = {
            "television": options.get("television_api"),
            "movie": options.get("movie_api"),
        }.get(media)

        if api is None:
            raise MnamerException("No provider specified for %s type" % media)
        keys = {
            "tmdb": options.get("api_key_tmdb"),
            "tvdb": options.get("api_key_tvdb"),
        }

        provider_search.providers[media] = provider_factory(
            api, api_key=keys.get(api)
        )
    for result in provider_search.providers[media].search(id_key, **metadata):
        yield result  # pragma: no cover
Exemple #24
0
def meta_parse(path, media=None):
    """ Uses guessit to parse metadata from a filename
    """
    common_country_codes = {"AU", "RUS", "UK", "US"}

    media = {"television": "episode", "tv": "episode", "movie": "movie"}.get(
        media
    )
    with catch_warnings():
        filterwarnings("ignore", category=Warning)
        data = dict(guessit(path, {"type": media}))
    media_type = data.get("type") if path else "unknown"

    # Parse movie metadata
    if media_type == "movie":
        meta = MetadataMovie()
        if "title" in data:
            meta["title"] = data["title"]
        if "year" in data:
            meta["date"] = "%s-01-01" % data["year"]
        meta["media"] = "movie"

    # Parse television metadata
    elif media_type == "episode":
        meta = MetadataTelevision()
        if "title" in data:
            meta["series"] = data["title"]
            if "year" in data:
                meta["series"] += " (%d)" % data["year"]
        if "season" in data:
            meta["season"] = str(data["season"])
        if "date" in data:
            meta["date"] = str(data["date"])
        if "episode" in data:
            if isinstance(data["episode"], (list, tuple)):
                meta["episode"] = str(sorted(data["episode"])[0])
            else:
                meta["episode"] = str(data["episode"])

    # Exit early if media type cannot be determined
    else:
        raise MnamerException("Could not determine media type")

    # Parse non-media specific fields
    quality_fields = [
        field
        for field in data
        if field
        in [
            "audio_codec",
            "audio_profile",
            "screen_size",
            "video_codec",
            "video_profile",
        ]
    ]
    for field in quality_fields:
        if "quality" not in meta:
            meta["quality"] = data[field]
        else:
            meta["quality"] += " " + data[field]
    if "release_group" in data:
        release_group = data["release_group"]

        # Sometimes country codes can get incorrectly detected as a scene group
        if "series" in meta and release_group.upper() in common_country_codes:
            meta["series"] += " (%s)" % release_group.upper()
        else:
            meta["group"] = data["release_group"]
    meta["extension"] = file_extension(path)
    return meta