Merge branch 'develop'

pull/54/head
l3uddz 7 years ago
commit b4fd01ded5

1
.gitignore vendored

@ -31,5 +31,6 @@ __pycache__/
# PyInstaller # PyInstaller
build/ build/
dist/
*.manifest *.manifest
*.spec *.spec

@ -1,5 +1,5 @@
# Traktarr # Traktarr
Script to add new TV series & movies to Sonarr/Radarr based on Trakt lists. Script to add new shows & movies to Sonarr/Radarr based on Trakt lists.
# Requirements # Requirements
1. Python 3.5 or higher (`sudo apt install python3 python3-pip`). 1. Python 3.5 or higher (`sudo apt install python3 python3-pip`).
@ -127,21 +127,21 @@ Options:
``` ```
## TV Shows ## Shows
``` ```
Usage: python3 traktarr.py shows [OPTIONS] Usage: python3 traktarr.py shows [OPTIONS]
Add new series to Sonarr. Add new shows to Sonarr.
Options: Options:
-t, --list-type [anticipated|trending|popular] -t, --list-type [anticipated|trending|popular]
Trakt list to process. [required] Trakt list to process. [required]
-l, --add-limit INTEGER Limit number of series added to Sonarr. -l, --add-limit INTEGER Limit number of shows added to Sonarr.
[default: 0] [default: 0]
-d, --add-delay FLOAT Seconds between each add request to Sonarr. -d, --add-delay FLOAT Seconds between each add request to Sonarr.
[default: 2.5] [default: 2.5]
--no-search Disable search when adding series to Sonarr. --no-search Disable search when adding shows to Sonarr.
--help Show this message and exit. --help Show this message and exit.
``` ```

@ -46,12 +46,12 @@ class Sonarr:
if req.status_code == 200: if req.status_code == 200:
resp_json = req.json() resp_json = req.json()
log.debug("Found %d series", len(resp_json)) log.debug("Found %d shows", len(resp_json))
return resp_json return resp_json
else: else:
log.error("Failed to retrieve all series, request response: %d", req.status_code) log.error("Failed to retrieve all shows, request response: %d", req.status_code)
except Exception: except Exception:
log.exception("Exception retrieving series: ") log.exception("Exception retrieving show: ")
return None return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
@ -64,6 +64,7 @@ class Sonarr:
if req.status_code == 200: if req.status_code == 200:
resp_json = req.json() resp_json = req.json()
log.debug("Found %d quality profiles", len(resp_json))
for profile in resp_json: for profile in resp_json:
if profile['name'].lower() == profile_name.lower(): if profile['name'].lower() == profile_name.lower():
log.debug("Found id of %s profile: %d", profile_name, profile['id']) log.debug("Found id of %s profile: %d", profile_name, profile['id'])
@ -76,12 +77,57 @@ class Sonarr:
return None return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def add_series(self, series_tvdbid, series_title, series_title_slug, profile_id, root_folder, search_missing=False): def get_tag_id(self, tag_name):
try:
# make request
req = requests.get(urljoin(self.server_url, 'api/tag'), headers=self.headers, timeout=30)
log.debug("Request URL: %s", req.url)
log.debug("Request Response: %d", req.status_code)
if req.status_code == 200:
resp_json = req.json()
log.debug("Found %d tags", len(resp_json))
for tag in resp_json:
if tag['label'].lower() == tag_name.lower():
log.debug("Found id of %s tag: %d", tag_name, tag['id'])
return tag['id']
log.debug("Tag %s with id %d did not match %s", tag['label'], tag['id'], tag_name)
else:
log.error("Failed to retrieve all tags, request response: %d", req.status_code)
except Exception:
log.exception("Exception retrieving id of tag %s: ", tag_name)
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def get_tags(self):
tags = {}
try:
# make request
req = requests.get(urljoin(self.server_url, 'api/tag'), headers=self.headers, timeout=30)
log.debug("Request URL: %s", req.url)
log.debug("Request Response: %d", req.status_code)
if req.status_code == 200:
resp_json = req.json()
log.debug("Found %d tags", len(resp_json))
for tag in resp_json:
tags[tag['label']] = tag['id']
return tags
else:
log.error("Failed to retrieve all tags, request response: %d", req.status_code)
except Exception:
log.exception("Exception retrieving tags: ")
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def add_series(self, series_tvdbid, series_title, series_title_slug, profile_id, root_folder, tag_ids=None,
search_missing=False):
try: try:
# generate payload # generate payload
payload = { payload = {
'tvdbId': series_tvdbid, 'title': series_title, 'titleSlug': series_title_slug, 'tvdbId': series_tvdbid, 'title': series_title, 'titleSlug': series_title_slug,
'qualityProfileId': profile_id, 'images': [], 'qualityProfileId': profile_id, 'tags': [] if not tag_ids or not isinstance(tag_ids, list) else tag_ids,
'images': [],
'seasons': [], 'seasonFolder': True, 'seasons': [], 'seasonFolder': True,
'monitored': True, 'rootFolderPath': root_folder, 'monitored': True, 'rootFolderPath': root_folder,
'addOptions': {'ignoreEpisodesWithFiles': False, 'addOptions': {'ignoreEpisodesWithFiles': False,
@ -105,5 +151,5 @@ class Sonarr:
log.error("Failed to add %s (%d), unexpected response:\n%s", series_title, series_tvdbid, req.text) log.error("Failed to add %s (%d), unexpected response:\n%s", series_title, series_tvdbid, req.text)
return False return False
except Exception: except Exception:
log.exception("Exception adding series %s (%d): ", series_title, series_tvdbid) log.exception("Exception adding show %s (%d): ", series_title, series_tvdbid)
return None return None

@ -16,7 +16,11 @@ base_config = {
'url': 'http://localhost:8989', 'url': 'http://localhost:8989',
'api_key': '', 'api_key': '',
'profile': 'HD-1080p', 'profile': 'HD-1080p',
'root_folder': '/tv/' 'root_folder': '/tv/',
'tags': {
'amzn': ['hbo', 'amc', 'usa network', 'tnt', 'starz', 'the cw', 'fx', 'fox', 'abc', 'nbc', 'cbs', 'tbs',
'amazon', 'syfy', 'cinemax']
}
}, },
'radarr': { 'radarr': {
'url': 'http://localhost:7878', 'url': 'http://localhost:7878',

@ -8,17 +8,29 @@ log = logger.get_logger(__name__)
# SONARR # SONARR
############################################################ ############################################################
def sonarr_series_tag_id_from_network(profile_tags, network_tags, network):
try:
for tag_name, tag_networks in network_tags.items():
for tag_network in tag_networks:
if tag_network.lower() in network.lower() and tag_name.lower() in profile_tags:
log.debug("Using %s tag for network: %s", tag_name, network)
return [profile_tags[tag_name.lower()]]
except Exception:
log.exception("Exception determining tag to use for network %s: ", network)
return None
def sonarr_series_to_tvdb_dict(sonarr_series): def sonarr_series_to_tvdb_dict(sonarr_series):
series = {} series = {}
try: try:
for tmp in sonarr_series: for tmp in sonarr_series:
if 'tvdbId' not in tmp: if 'tvdbId' not in tmp:
log.debug("Could not handle series: %s", tmp['title']) log.debug("Could not handle show: %s", tmp['title'])
continue continue
series[tmp['tvdbId']] = tmp series[tmp['tvdbId']] = tmp
return series return series
except Exception: except Exception:
log.exception("Exception processing sonarr series to tvdb dict: ") log.exception("Exception processing Sonarr shows to TVDB dict: ")
return None return None
@ -47,11 +59,11 @@ def sonarr_remove_existing_series(sonarr_series, trakt_series):
new_series_list.append(tmp) new_series_list.append(tmp)
log.debug("Filtered %d trakt shows to %d shows that weren't already in Sonarr", len(trakt_series), log.debug("Filtered %d Trakt shows to %d shows that weren't already in Sonarr", len(trakt_series),
len(new_series_list)) len(new_series_list))
return new_series_list return new_series_list
except Exception: except Exception:
log.exception("Exception removing existing series from trakt list: ") log.exception("Exception removing existing shows from Trakt list: ")
return None return None
@ -133,7 +145,7 @@ def trakt_blacklisted_show_runtime(show, lowest_runtime):
blacklisted = True blacklisted = True
elif int(show['show']['runtime']) < lowest_runtime: elif int(show['show']['runtime']) < lowest_runtime:
log.debug("%s was blacklisted because it had a runtime of: %d", show['show']['title'], log.debug("%s was blacklisted because it had a runtime of: %d", show['show']['title'],
show['movie']['runtime']) show['show']['runtime'])
blacklisted = True blacklisted = True
except Exception: except Exception:
@ -173,7 +185,7 @@ def radarr_movies_to_tmdb_dict(radarr_movies):
movies[tmp['tmdbId']] = tmp movies[tmp['tmdbId']] = tmp
return movies return movies
except Exception: except Exception:
log.exception("Exception processing radarr movies to tmdb dict: ") log.exception("Exception processing Radarr movies to TMDB dict: ")
return None return None
@ -202,11 +214,11 @@ def radarr_remove_existing_movies(radarr_movies, trakt_movies):
new_movies_list.append(tmp) new_movies_list.append(tmp)
log.debug("Filtered %d trakt movies to %d movies that weren't already in Radarr", len(trakt_movies), log.debug("Filtered %d Trakt movies to %d movies that weren't already in Radarr", len(trakt_movies),
len(new_movies_list)) len(new_movies_list))
return new_movies_list return new_movies_list
except Exception: except Exception:
log.exception("Exception removing existing movies from trakt list: ") log.exception("Exception removing existing movies from Trakt list: ")
return None return None

@ -19,7 +19,7 @@ log = logger.get_logger('traktarr')
# Click # Click
@click.group(help='Add new series/movies to Sonarr & Radarr from Trakt.') @click.group(help='Add new shows & movies to Sonarr/Radarr from Trakt lists.')
def app(): def app():
pass pass
@ -28,12 +28,12 @@ def app():
# SHOWS # SHOWS
############################################################ ############################################################
@app.command(help='Add new series to Sonarr.') @app.command(help='Add new shows to Sonarr.')
@click.option('--list-type', '-t', type=click.Choice(['anticipated', 'trending', 'popular']), @click.option('--list-type', '-t', type=click.Choice(['anticipated', 'trending', 'popular']),
help='Trakt list to process.', required=True) help='Trakt list to process.', required=True)
@click.option('--add-limit', '-l', default=0, help='Limit number of series added to Sonarr.', show_default=True) @click.option('--add-limit', '-l', default=0, help='Limit number of shows added to Sonarr.', show_default=True)
@click.option('--add-delay', '-d', default=2.5, help='Seconds between each add request to Sonarr.', show_default=True) @click.option('--add-delay', '-d', default=2.5, help='Seconds between each add request to Sonarr.', show_default=True)
@click.option('--no-search', is_flag=True, help='Disable search when adding series to Sonarr.') @click.option('--no-search', is_flag=True, help='Disable search when adding shows to Sonarr.')
def shows(list_type, add_limit=0, add_delay=2.5, no_search=False): def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
added_shows = 0 added_shows = 0
@ -61,13 +61,21 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
else: else:
log.info("Retrieved Profile ID for %s: %d", cfg.sonarr.profile, profile_id) log.info("Retrieved Profile ID for %s: %d", cfg.sonarr.profile, profile_id)
# retrieve profile tags
profile_tags = sonarr.get_tags()
if profile_tags is None:
log.error("Aborting due to failure to retrieve Tag ID's")
return
else:
log.info("Retrieved %d Tag ID's", len(profile_tags))
# get sonarr series list # get sonarr series list
sonarr_series_list = sonarr.get_series() sonarr_series_list = sonarr.get_series()
if not sonarr_series_list: if not sonarr_series_list:
log.error("Aborting due to failure to retrieve Sonarr series list") log.error("Aborting due to failure to retrieve Sonarr shows list")
return return
else: else:
log.info("Retrieved Sonarr series list, series found: %d", len(sonarr_series_list)) log.info("Retrieved Sonarr shows list, shows found: %d", len(sonarr_series_list))
# get trakt series list # get trakt series list
trakt_series_list = None trakt_series_list = None
@ -81,23 +89,23 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
log.error("Aborting due to unknown Trakt list type") log.error("Aborting due to unknown Trakt list type")
return return
if not trakt_series_list: if not trakt_series_list:
log.error("Aborting due to failure to retrieve Trakt %s series list", list_type) log.error("Aborting due to failure to retrieve Trakt %s shows list", list_type)
return return
else: else:
log.info("Retrieved Trakt %s series list, series found: %d", list_type, len(trakt_series_list)) log.info("Retrieved Trakt %s shows list, shows found: %d", list_type, len(trakt_series_list))
# build filtered series list without series that exist in sonarr # build filtered series list without series that exist in sonarr
processed_series_list = helpers.sonarr_remove_existing_series(sonarr_series_list, trakt_series_list) processed_series_list = helpers.sonarr_remove_existing_series(sonarr_series_list, trakt_series_list)
if not processed_series_list: if not processed_series_list:
log.error("Aborting due to failure to remove existing Sonarr series from retrieved Trakt series list") log.error("Aborting due to failure to remove existing Sonarr shows from retrieved Trakt shows list")
return return
else: else:
log.info("Removed existing Sonarr series from Trakt series list, series left to process: %d", log.info("Removed existing Sonarr shows from Trakt shows list, shows left to process: %d",
len(processed_series_list)) len(processed_series_list))
# sort filtered series list by highest votes # sort filtered series list by highest votes
sorted_series_list = sorted(processed_series_list, key=lambda k: k['show']['votes'], reverse=True) sorted_series_list = sorted(processed_series_list, key=lambda k: k['show']['votes'], reverse=True)
log.info("Sorted series list to process by highest votes") log.info("Sorted shows list to process by highest votes")
# loop series_list # loop series_list
log.info("Processing list now...") log.info("Processing list now...")
@ -109,13 +117,18 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
', '.join(series['show']['genres']), series['show']['network'], ', '.join(series['show']['genres']), series['show']['network'],
series['show']['country'].upper()) series['show']['country'].upper())
# determine which tags to use when adding this series
use_tags = helpers.sonarr_series_tag_id_from_network(profile_tags, cfg.sonarr.tags,
series['show']['network'])
# add show to sonarr # add show to sonarr
if sonarr.add_series(series['show']['ids']['tvdb'], series['show']['title'], if sonarr.add_series(series['show']['ids']['tvdb'], series['show']['title'],
series['show']['ids']['slug'], profile_id, cfg.sonarr.root_folder, not no_search): series['show']['ids']['slug'], profile_id, cfg.sonarr.root_folder, use_tags,
log.info("ADDED %s (%d)", series['show']['title'], series['show']['year']) not no_search):
log.info("ADDED %s (%d) with tags: %s", series['show']['title'], series['show']['year'], use_tags)
added_shows += 1 added_shows += 1
else: else:
log.error("FAILED adding %s (%d)", series['show']['title'], series['show']['year']) log.error("FAILED adding %s (%d) with tags: %s", series['show']['title'], series['show']['year'],
use_tags)
# stop adding shows, if added_shows >= add_limit # stop adding shows, if added_shows >= add_limit
if add_limit and added_shows >= add_limit: if add_limit and added_shows >= add_limit:
@ -125,9 +138,9 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
time.sleep(add_delay) time.sleep(add_delay)
except Exception: except Exception:
log.exception("Exception while processing series %s: ", series['show']['title']) log.exception("Exception while processing show %s: ", series['show']['title'])
log.info("Added %d new shows to Sonarr", added_shows) log.info("Added %d new show(s) to Sonarr", added_shows)
@app.command(help='Add new movies to Radarr.') @app.command(help='Add new movies to Radarr.')
@ -227,7 +240,7 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False):
except Exception: except Exception:
log.exception("Exception while processing movie %s: ", movie['movie']['title']) log.exception("Exception while processing movie %s: ", movie['movie']['title'])
log.info("Added %d new movies to Radarr", added_movies) log.info("Added %d new movie(s) to Radarr", added_movies)
############################################################ ############################################################

Loading…
Cancel
Save