Sonarr: Added support for Language Profiles (Sonarr v3)

Closes #80
pull/105/head
desimaniac 5 years ago
parent ac08098a16
commit 44fb9a2fc7

@ -324,13 +324,14 @@ You can repeat this process for as many users as you like.
"radarr": {
"api_key": "",
"minimum_availability": "released",
"profile": "HD-1080p",
"quality": "HD-1080p",
"root_folder": "/movies/",
"url": "http://localhost:7878/"
},
"sonarr": {
"api_key": "",
"profile": "HD-1080p",
"language": "English",
"quality": "HD-1080p",
"root_folder": "/tv/",
"tags": {},
"url": "http://localhost:8989/"
@ -898,14 +899,14 @@ Radarr configuration.
"radarr": {
"api_key": "",
"minimum_availability": "released",
"profile": "HD-1080p",
"quality": "HD-1080p",
"root_folder": "/movies/",
"url": "http://localhost:7878"
},
```
`api_key` - Radarr's API Key.
`profile` - Profile that movies are assigned to.
`quality` - Quality Profile that movies are assigned to.
`minimum_availability` - The minimum availability the movies are set to.
@ -926,7 +927,8 @@ Sonarr configuration.
```json
"sonarr": {
"api_key": "",
"profile": "HD-1080p",
"language": "English",
"quality": "HD-1080p",
"root_folder": "/tv/",
"tags": {},
"url": "http://localhost:8989"
@ -935,7 +937,9 @@ Sonarr configuration.
`api_key` - Sonarr's API Key.
`profile` - Profile that TV shows are assigned to.
`language` - Language Profile that TV shows are assigned to. Only applies to Sonarr v3.
`quality` - Quality Profile that TV shows are assigned to.
`root_folder` - Root folder for TV shows.

@ -1,5 +1,6 @@
import os.path
from abc import ABC, abstractmethod
from distutils.version import LooseVersion as Version
import backoff
import requests
@ -67,7 +68,7 @@ class PVR(ABC):
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def get_profile_id(self, profile_name):
def get_quality_profile_id(self, profile_name):
try:
# make request
req = requests.get(
@ -83,21 +84,70 @@ class PVR(ABC):
resp_json = req.json()
for profile in resp_json:
if profile['name'].lower() == profile_name.lower():
log.debug("Found Profile ID for \'%s\': %d", profile_name, profile['id'])
log.debug("Found Quality Profile ID for \'%s\': %d", profile_name, profile['id'])
return profile['id']
log.debug("Profile \'%s\' with ID \'%d\' did not match Profile \'%s\'", profile['name'],
log.debug("Profile \'%s\' with ID \'%d\' did not match Quality Profile \'%s\'", profile['name'],
profile['id'], profile_name)
else:
log.error("Failed to retrieve all quality profiles, request response: %d", req.status_code)
except Exception:
log.exception("Exception retrieving ID of profile %s: ", profile_name)
log.exception("Exception retrieving ID of quality profile %s: ", profile_name)
return None
def _prepare_add_object_payload(self, title, title_slug, profile_id, root_folder):
@backoff.on_exception(backoff.expo, requests.exceptions.RequestException, max_tries=4, on_backoff=backoff_handler)
def get_language_profile_id(self, language_name):
try:
# check if sonarr is v3
# make request
ver_req = requests.get(
os.path.join(misc_str.ensure_endswith(self.server_url, "/"), 'api/system/status'),
headers=self.headers,
timeout=60,
allow_redirects=False
)
if ver_req.status_code == 200:
ver_resp_json = ver_req.json()
if not Version(ver_resp_json['version']) > Version('3'):
log.debug("Skipping Language Profile lookup because Sonarr version is \'%s\'.",
ver_resp_json['version'])
return None
except Exception:
log.exception("Exception verifying Sonarr version.")
return None
try:
# make request
req = requests.get(
os.path.join(misc_str.ensure_endswith(self.server_url, "/"), 'api/v3/languageprofile'),
headers=self.headers,
timeout=60,
allow_redirects=False
)
log.debug("Request URL: %s", req.url)
log.debug("Request Response: %d", req.status_code)
if req.status_code == 200:
resp_json = req.json()
for profile in resp_json:
if profile['name'].lower() == language_name.lower():
log.debug("Found Language Profile ID for \'%s\': %d", language_name, profile['id'])
return profile['id']
log.debug("Profile \'%s\' with ID \'%d\' did not match Language Profile \'%s\'", profile['name'],
profile['id'], language_name)
else:
log.error("Failed to retrieve all language profiles, request response: %d", req.status_code)
except Exception:
log.exception("Exception retrieving ID of language profile %s: ", language_name)
return None
def _prepare_add_object_payload(self, title, title_slug, quality_profile_id, root_folder):
return {
'title': title,
'titleSlug': title_slug,
'qualityProfileId': profile_id,
'qualityProfileId': quality_profile_id,
'images': [],
'monitored': True,
'rootFolderPath': root_folder,

@ -12,9 +12,9 @@ class Radarr(PVR):
return self._get_objects('api/movie')
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def add_movie(self, movie_tmdb_id, movie_title, movie_year, movie_title_slug, profile_id, root_folder,
def add_movie(self, movie_tmdb_id, movie_title, movie_year, movie_title_slug, quality_profile_id, root_folder,
min_availability_temp, search_missing=False):
payload = self._prepare_add_object_payload(movie_title, movie_title_slug, profile_id, root_folder)
payload = self._prepare_add_object_payload(movie_title, movie_title_slug, quality_profile_id, root_folder)
# replace radarr minimum_availability if supplied
if min_availability_temp == 'announced':

@ -42,9 +42,9 @@ class Sonarr(PVR):
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def add_series(self, series_tvdb_id, series_title, series_title_slug, profile_id, root_folder, tag_ids=None,
search_missing=False, series_type='standard'):
payload = self._prepare_add_object_payload(series_title, series_title_slug, profile_id, root_folder)
def add_series(self, series_tvdb_id, series_title, series_title_slug, quality_profile_id, language_profile_id,
root_folder, tag_ids=None, search_missing=False, series_type='standard'):
payload = self._prepare_add_object_payload(series_title, series_title_slug, quality_profile_id, root_folder)
payload = dict_merge(payload, {
'tvdbId': series_tvdb_id,
@ -57,4 +57,10 @@ class Sonarr(PVR):
}
})
return self._add_object('api/series', payload, identifier_field='tvdbId', identifier=series_tvdb_id)
if language_profile_id:
payload['languageProfileId'] = language_profile_id
endpoint = 'api/v3/series'
else:
endpoint = 'api/series'
return self._add_object(endpoint, payload, identifier_field='tvdbId', identifier=series_tvdb_id)

@ -37,27 +37,23 @@ class Config(object, metaclass=Singleton):
'core': {
'debug': False
},
'trakt': {
'client_id': '',
'client_secret': ''
},
'sonarr': {
'api_key': '',
'profile': 'HD-1080p',
'root_folder': '/tv/',
'tags': {
},
'url': 'http://localhost:8989/'
'notifications': {
'verbose': True
},
'radarr': {
'api_key': '',
'minimum_availability': 'released',
'profile': 'HD-1080p',
'root_folder': '/movies/',
'url': 'http://localhost:7878/'
'automatic': {
'movies': {
'interval': 20,
'anticipated': 3,
'trending': 3,
'popular': 3,
'boxoffice': 10
},
'omdb': {
'api_key': ''
'shows': {
'interval': 48,
'anticipated': 10,
'trending': 1,
'popular': 1
}
},
'filters': {
'shows': {
@ -85,23 +81,28 @@ class Config(object, metaclass=Singleton):
'rotten_tomatoes': ""
}
},
'automatic': {
'movies': {
'interval': 20,
'anticipated': 3,
'trending': 3,
'popular': 3,
'boxoffice': 10
'radarr': {
'api_key': '',
'minimum_availability': 'released',
'quality': 'HD-1080p',
'root_folder': '/movies/',
'url': 'http://localhost:7878/'
},
'shows': {
'interval': 48,
'anticipated': 10,
'trending': 1,
'popular': 1
}
'sonarr': {
'api_key': '',
'language': 'English',
'quality': 'HD-1080p',
'root_folder': '/tv/',
'tags': {
},
'notifications': {
'verbose': True
'url': 'http://localhost:8989/'
},
'omdb': {
'api_key': ''
},
'trakt': {
'client_id': '',
'client_secret': ''
}
}

@ -2,6 +2,9 @@
"core": {
"debug": false
},
"notifications": {
"verbose": false
},
"automatic": {
"movies": {
"anticipated": 3,
@ -17,9 +20,6 @@
"trending": 2
}
},
"notifications": {
"verbose": false
},
"filters": {
"movies": {
"disabled_for": [],
@ -93,18 +93,22 @@
},
"radarr": {
"api_key": "",
"profile": "HD-1080p",
"quality": "HD-1080p",
"minimum_availability": "released",
"url": "http://localhost:7878/",
"root_folder": "/movies/"
},
"sonarr": {
"api_key": "",
"profile": "HD-1080p",
"language": "English",
"quality": "HD-1080p",
"url": "http://localhost:8989/",
"root_folder": "/tv/",
"tags": {}
},
"omdb": {
"api_key": ""
},
"trakt": {
"client_id": "",
"client_secret": ""

@ -56,6 +56,10 @@ def app(config, cachefile, logfile):
cfg['filters']['movies']['blacklisted_title_keywords'] = cfg['filters']['movies']['blacklist_title_keywords']
if cfg.filters.movies.rating_limit:
cfg['filters']['movies']['rotten_tomatoes'] = cfg['filters']['movies']['rating_limit']
if cfg.radarr.profile:
cfg['radarr']['quality'] = cfg['radarr']['profile']
if cfg.sonarr.profile:
cfg['sonarr']['quality'] = cfg['sonarr']['profile']
# Load logger
from misc.log import logger
@ -104,14 +108,24 @@ def validate_pvr(pvr, pvr_type, notifications):
log.info("Validated %s URL & API Key.", pvr_type)
def get_profile_id(pvr, profile):
# retrieve profile id for requested profile
profile_id = pvr.get_profile_id(profile)
if not profile_id or not profile_id > 0:
log.error("Aborting due to failure to retrieve Profile ID for: %s", profile)
def get_quality_profile_id(pvr, quality_profile):
# retrieve profile id for requested quality profile
quality_profile_id = pvr.get_quality_profile_id(quality_profile)
if not quality_profile_id or not quality_profile_id > 0:
log.error("Aborting due to failure to retrieve Quality Profile ID for: %s", quality_profile)
exit()
log.info("Retrieved Profile ID for \'%s\': %d", profile, profile_id)
return profile_id
log.info("Retrieved Quality Profile ID for \'%s\': %d", quality_profile, quality_profile_id)
return quality_profile_id
def get_language_profile_id(pvr, language_profile):
# retrieve profile id for requested language profile
language_profile_id = pvr.get_language_profile_id(language_profile)
if not language_profile_id or not language_profile_id > 0:
log.error("No Language Profile ID for: %s", language_profile)
else:
log.info("Retrieved Language Profile ID for \'%s\': %d", language_profile, language_profile_id)
return language_profile_id
def get_profile_tags(pvr):
@ -188,8 +202,11 @@ def show(show_id, folder=None, no_search=False):
log.info("Retrieved Trakt show information for \'%s\': \'%s (%s)\'", show_id, series_title, series_year)
# profile id
profile_id = get_profile_id(sonarr, cfg.sonarr.profile)
# quality profile id
quality_profile_id = get_quality_profile_id(sonarr, cfg.sonarr.quality)
# language profile id
language_profile_id = get_language_profile_id(sonarr, cfg.sonarr.language)
# profile tags
profile_tags = None
@ -214,7 +231,8 @@ def show(show_id, folder=None, no_search=False):
if sonarr.add_series(trakt_show['ids']['tvdb'],
series_title,
trakt_show['ids']['slug'],
profile_id,
quality_profile_id,
language_profile_id,
cfg.sonarr.root_folder,
use_tags,
not no_search,
@ -331,7 +349,12 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde
validate_trakt(trakt, notifications)
validate_pvr(sonarr, 'Sonarr', notifications)
profile_id = get_profile_id(sonarr, cfg.sonarr.profile)
# quality profile id
quality_profile_id = get_quality_profile_id(sonarr, cfg.sonarr.quality)
# language profile id
language_profile_id = get_language_profile_id(sonarr, cfg.sonarr.language)
profile_tags = get_profile_tags(sonarr) if cfg.sonarr.tags else None
pvr_objects_list = get_objects(sonarr, 'Sonarr', notifications)
@ -480,7 +503,8 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde
if sonarr.add_series(series['show']['ids']['tvdb'],
series_title,
series['show']['ids']['slug'],
profile_id,
quality_profile_id,
language_profile_id,
cfg.sonarr.root_folder,
use_tags,
not no_search,
@ -571,7 +595,8 @@ def movie(movie_id, folder=None, minimum_availability=None, no_search=False):
validate_trakt(trakt, False)
validate_pvr(radarr, 'Radarr', False)
profile_id = get_profile_id(radarr, cfg.radarr.profile)
# quality profile id
quality_profile_id = get_quality_profile_id(radarr, cfg.radarr.quality)
# get trakt movie
trakt_movie = trakt.get_movie(movie_id)
@ -590,7 +615,7 @@ def movie(movie_id, folder=None, minimum_availability=None, no_search=False):
trakt_movie['title'],
trakt_movie['year'],
trakt_movie['ids']['slug'],
profile_id,
quality_profile_id,
cfg.radarr.root_folder,
cfg.radarr.minimum_availability,
not no_search):
@ -717,7 +742,8 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', rotten_tomatoes=
validate_trakt(trakt, notifications)
validate_pvr(radarr, 'Radarr', notifications)
profile_id = get_profile_id(radarr, cfg.radarr.profile)
# quality profile id
quality_profile_id = get_quality_profile_id(radarr, cfg.radarr.quality)
pvr_objects_list = get_objects(radarr, 'Radarr', notifications)
@ -867,7 +893,7 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', rotten_tomatoes=
movie_title,
movie_year,
sorted_movie['movie']['ids']['slug'],
profile_id,
quality_profile_id,
cfg.radarr.root_folder,
cfg.radarr.minimum_availability,
not no_search):

Loading…
Cancel
Save