diff --git a/.gitignore b/.gitignore index 44ddfdd..70642c3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /.gitignore /.idea /build -/plex_auto_collections.spec \ No newline at end of file +/plex_auto_collections.spec +/__pycache__/ diff --git a/README.md b/README.md index 052a37f..2967fc4 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ ![https://i.imgur.com/iHAYFIZ.png](https://i.imgur.com/iHAYFIZ.png) Plex Auto Collections is a Python 3 script/[standalone builds](https://github.com/vladimir-tutin/Plex-Auto-Collections/tree/master/dist) that works off a configuration file to create/update Plex collection. Collection management with this tool can be automated -in a varying degree of customizability. Supports IMDB and TMDb lists as well as built in Plex +in a varying degree of customizability. Supports IMDB, TMDb, and Trakt lists as well as built in Plex filters such as actors, genres, year, studio and more. For more filters refer to the [plexapi.video.Movie](https://python-plexapi.readthedocs.io/en/latest/modules/video.html#plexapi.video.Movie) documentation. Not everything has been tested, so results may vary based off the filter. A TMDb api key is required to scan TMDb URLs. When parsing IMDB or TMBd lists the script will create a list of movies that are missing from Plex. If an TMDb and -Radarr api-key are supplied then the option will be presented to pass the list of movies along to Radarr. +Radarr api-key are supplied then the option will be presented to pass the list of movies along to Radarr. Trakt lists will be matched against items in both a Movie and a TV library, each. As well as updating collections based off configuration files there is the ability to add new collections based off filters, delete collections, search for collections and manage the collections in the configuration file. Collection @@ -29,7 +29,7 @@ If using TMDb lists be sure to include your TMDb api-key. If you do not have an [document](https://developers.themoviedb.org/3/getting-started/introduction). If you do not want it to have the option to submit movies that are missing from IMDB or TMBd lists do not include the -api-key for Radarr. +api-key for Radarr. Radarr support has not been tested with Trakt lists. Sonarr support has not yet been implemented. Adding a summary to the collection is possible by either pulling the overview from TMDb or by using a custom entry. To use a TMDb entry a TMDb api-key as well as language is required, the default language is set to en. Match the following @@ -55,7 +55,7 @@ Adding a poster can be done by adding the URL to the image. details: tmdb-summary: 328 poster: https://i.imgur.com/QMjbyCX.png - + Local assets are supported by running the script with the image server running. If there are no details filled out for the poster in the configuration file and the image server is running the script will attempt to match a collection name with an image file of the same name. Images should be placed in the ./images folder. Port forwarding is not required. @@ -65,6 +65,13 @@ If you want movies to add to Radarr but not automatically search, change search In order to find your Plex token follow [this guide](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/). +Trakt lists require a client id and client secret. +1. [Create](https://trakt.tv/oauth/applications/new) a Trakt API application. +2. Enter a `Name` for the application. +3. Enter `urn:ietf:wg:oauth:2.0:oob` for `Redirect uri`. +4. Click the `SAVE APP` button. +5. Record the `Client ID` and `Client Secret`. + Library should be the name of the Plex library that you are wanting to search and create collections in. Main filters allowed are actors, imdb-list as well as many attributes that can found in the [plexapi.video.Movie @@ -80,38 +87,55 @@ each must match what Plex has including special characters in order to match. If you do not want to use subfilters simply remove the section. **Once complete it should look like** - - collections: - Jurassic Park: - tmdb-list: https://www.themoviedb.org/collection/328 - details: - tmdb-summary: 328 - poster: https://i.imgur.com/QMjbyCX.png - 1080p Documentaries: - genres: Documentary - subfilters: - video-resolution: 1080 - details: - summary: A collection of 1080p Documentaries - Daniel Craig only James Bonds: - imdb-list: https://www.imdb.com/list/ls006405458/ - subfilters: - actors: Daniel Craig - plex: - library: Movies - token: ################### - url: http://192.168.1.5:32400 - radarr: - url: http://192.168.1.5:7878/radarr/ - token: ########################### - quality_profile_id: 4 - search: true - tmdb: - apikey: ############################ - language: en - image-server: - host: 192.168.1.41 - port: 5000 +``` +collections: + Jurassic Park: + tmdb-list: https://www.themoviedb.org/collection/328 + details: + tmdb-summary: 328 + poster: https://i.imgur.com/QMjbyCX.png + 1080p Documentaries: + genres: Documentary + subfilters: + video-resolution: 1080 + details: + summary: A collection of 1080p Documentaries + Daniel Craig only James Bonds: + imdb-list: https://www.imdb.com/list/ls006405458/ + subfilters: + actors: Daniel Craig + Disney+ TV: + trakt-list: https://trakt.tv/users/drew-casteo/lists/disney-tv-shows + Disney+ Movies: + trakt-list: https://trakt.tv/users/drew-casteo/lists/disney-movies +plex: + movie_library: Movies + show_library: TV Shows + token: ################ + url: http://192.168.1.5:32400 +radarr: + url: http://192.168.1.5:7878/radarr/ + token: ################ + quality_profile_id: 4 + search: true +tmdb: + apikey: ################ + language: en +trakt: + client_id: ################ + client_secret: ################ + # Below is filled in automatically when the script is run + authorization: + access_token: + token_type: + expires_in: + refresh_token: + scope: + created_at: +image-server: + host: 192.168.1.41 + port: 5000 +``` # Usage [Standalone binaries](https://github.com/vladimir-tutin/Plex-Auto-Collections/tree/master/dist) have been created for both Windows and Linux. diff --git a/config.yml.template b/config.yml.template index 7a061fe..420a0a5 100644 --- a/config.yml.template +++ b/config.yml.template @@ -14,23 +14,34 @@ collections: actors: Dave Chappelle details: tmdb-summary: 4169 - Marvel Cinematic Universe: - trakt-list: https://trakt.tv/users/signoftheshadow/lists/mcu + Disney+ TV: + trakt-list: https://trakt.tv/users/drew-casteo/lists/disney-tv-shows + Disney+ Movies: + trakt-list: https://trakt.tv/users/drew-casteo/lists/disney-movies plex: - library: Movies + movie_library: Movies + show_library: TV Shows token: ################### url: http://192.168.1.5:32400 radarr: url: http://192.168.1.5:7878/radarr/ token: ########################### quality_profile_id: 4 - search: true + search: false tmdb: apikey: ############################ language: en trakt: client_id: ############################ client_secret: ############################ + # Below is filled in automatically when the script is run + authorization: + access_token: + token_type: + expires_in: + refresh_token: + scope: + created_at: image-server: host: 0.0.0.0 port: 5000 diff --git a/config_tools.py b/config_tools.py index a56dd39..3b29b5a 100755 --- a/config_tools.py +++ b/config_tools.py @@ -5,11 +5,16 @@ import socket from plexapi.server import PlexServer from plexapi.video import Movie +from plexapi.video import Show +from plexapi.library import MovieSection +from plexapi.library import ShowSection from plex_tools import get_actor_rkey from plex_tools import add_to_collection from plex_tools import get_collection from radarr_tools import add_to_radarr from imdb_tools import tmdb_get_summary +from trakt import Trakt +import trakt_helpers class Config: def __init__(self): @@ -29,10 +34,16 @@ def __init__(self): config = Config().plex self.url = config['url'] self.token = config['token'] - self.library = config['library'] - self.Server = PlexServer(self.url, self.token) - self.MovieLibrary = self.Server.library.section(self.library) + self.timeout = 60 + # self.library = config['library'] + self.movie_library = config['movie_library'] + self.show_library = config['show_library'] + self.Server = PlexServer(self.url, self.token, timeout=self.timeout) + self.Sections = self.Server.library.sections() + self.MovieLibrary = next((s for s in self.Sections if (s.title == self.movie_library) and (isinstance(s, MovieSection))), None) + self.ShowLibrary = next((s for s in self.Sections if (s.title == self.show_library) and (isinstance(s, ShowSection))), None) self.Movie = Movie + self.Show = Show class Radarr: @@ -50,11 +61,18 @@ def __init__(self): self.language = config['language'] -class Trakt: +class TraktClient: def __init__(self): config = Config().trakt self.client_id = config['client_id'] self.client_secret = config['client_secret'] + self.authorization = config['authorization'] + Trakt.configuration.defaults.client(self.client_id, self.client_secret) + # Try the token from the config + self.updated_authorization = trakt_helpers.authenticate(self.authorization) + Trakt.configuration.defaults.oauth.from_response(self.updated_authorization) + if self.updated_authorization != self.authorization: + trakt_helpers.save_authorization(Config().config_path, self.updated_authorization) class ImageServer: @@ -91,24 +109,45 @@ def update_from_config(plex, skip_radarr=False): if m == "actors" or m == "actor": v = get_actor_rkey(plex, v) try: - missing = add_to_collection(plex, m, v, c, subfilters) + missing_movies, missing_shows = add_to_collection(plex, m, v, c, subfilters) except UnboundLocalError: # No sub-filters - missing = add_to_collection(plex, m, v, c) - except KeyError as e: + missing_movies, missing_shows = add_to_collection(plex, m, v, c) + except (KeyError, ValueError) as e: print(e) - missing = False - if missing: + missing_movies = False + missing_shows = False + if missing_movies: if "imdb" in m: m = "IMDb" + elif "trakt in m": + m = "Trakt" else: m = "TMDb" - print("{} missing movies from {} List: {}".format(len(missing), m, v)) + print("{} missing movies from {} List: {}".format(len(missing_movies), m, v)) if not skip_radarr: if input("Add missing movies to Radarr? (y/n): ").upper() == "Y": - add_to_radarr(missing) + add_to_radarr(missing_movies) + if missing_shows: + if "trakt in m": + m = "Trakt" + else: + m = "TMDb" + print("{} missing shows from {} List: {}".format(len(missing_shows), m, v)) + # if not skip_sonarr: + # if input("Add missing shows to Sonarr? (y/n): ").upper() == "Y": + # add_to_radarr(missing_shows) + # Multiple collections of the same name if "details" in collections[c]: + # Check if there are multiple collections with the same name + movie_collections = plex.MovieLibrary.search(title=c, libtype="collection") + show_collections = plex.ShowLibrary.search(title=c, libtype="collection") + if len(movie_collections + show_collections) > 1: + print("Multiple collections named {}.\nUpdate of \"details\" is currently unsupported.".format(c)) + continue + plex_collection = get_collection(plex, c) for dt_m in collections[c]["details"]: - rkey = get_collection(plex, c).ratingKey + rkey = plex_collection.ratingKey + subtype = plex_collection.subtype dt_v = collections[c]["details"][dt_m] if "summary" in dt_m: if "tmdb" in dt_m: @@ -116,9 +155,13 @@ def update_from_config(plex, skip_radarr=False): dt_v = tmdb_get_summary(dt_v, "overview") except AttributeError: dt_v = tmdb_get_summary(dt_v, "biography") + if subtype == 'movie': + library_name = plex.MovieLibrary + elif subtype == 'show': + library_name = plex.ShowLibrary - library_name = plex.library - section = plex.Server.library.section(library_name).key + #section = plex.Server.library.section(library_name).key + section = library_name.key url = plex.url + "/library/sections/" + str(section) + "/all" querystring = {"type":"18", diff --git a/imdb_tools.py b/imdb_tools.py index e610ba9..8e88e83 100755 --- a/imdb_tools.py +++ b/imdb_tools.py @@ -6,8 +6,6 @@ from tmdbv3api import Collection from tmdbv3api import Person import config_tools -from urllib.parse import urlparse -import trakt def imdb_get_movies(plex, data): @@ -116,53 +114,6 @@ def tmdb_get_movies(plex, data): return matched, missing -def trakt_get_movies(plex, data): - tmdb = TMDb() - tmdb.api_key = config_tools.TMDB().apikey # Set TMDb api key for Collection - trakt.Trakt.configuration.defaults.client(config_tools.Trakt().client_id, config_tools.Trakt().client_secret) - movie = Movie() - tmdb.api_key = config_tools.TMDB().apikey - trakt_url = data - if trakt_url[-1:] == " ": - trakt_url = trakt_url[:-1] - imdb_map = {} - library_language = plex.MovieLibrary.language - trakt_list_path = urlparse(trakt_url).path - trakt_list_items = trakt.Trakt[trakt_list_path].items() - title_ids = [m.pk[1] for m in trakt_list_items if isinstance(m, trakt.objects.movie.Movie)] - - plex_movies = plex.MovieLibrary.all() - if title_ids: - for m in plex_movies: - if 'themoviedb://' in m.guid: - if not tmdb.api_key == "None": - tmdb_id = m.guid.split('themoviedb://')[1].split('?')[0] - tmdbapi = movie.details(tmdb_id) - imdb_id = tmdbapi.imdb_id - else: - imdb_id = None - elif 'imdb://' in m.guid: - imdb_id = m.guid.split('imdb://')[1].split('?')[0] - else: - imdb_id = None - - if imdb_id and imdb_id in title_ids: - imdb_map[imdb_id] = m - else: - imdb_map[m.ratingKey] = m - - matched_imbd_movies = [] - missing_imdb_movies = [] - for imdb_id in title_ids: - movie = imdb_map.pop(imdb_id, None) - if movie: - matched_imbd_movies.append(plex.Server.fetchItem(movie.ratingKey)) - else: - missing_imdb_movies.append(imdb_id) - - return matched_imbd_movies, missing_imdb_movies - - def tmdb_get_summary(data, type): collection = Collection() person = Person() diff --git a/plex_auto_collections.py b/plex_auto_collections.py index e3d7f76..cfa9dbc 100755 --- a/plex_auto_collections.py +++ b/plex_auto_collections.py @@ -3,6 +3,7 @@ import threading from plexapi.video import Movie +from plexapi.video import Show import image_server import plex_tools @@ -27,7 +28,11 @@ def append_collection(config_update=None): finished = False while not finished: try: - method = input("Add Movie(m), Actor(a), IMDb/TMDb/Trakt List(l), Custom(c)?: ") + collection_type = selected_collection.subtype + if collection_type == 'movie': + method = input("Add Movie(m), Actor(a), IMDb/TMDb/Trakt List(l), Custom(c)?: ") + else: + method = input("Add Show(s), Actor(a), IMDb/TMDb/Trakt List(l), Custom(c)?: ") if method == "m": if not config_update: method = "movie" @@ -60,6 +65,38 @@ def append_collection(config_update=None): else: print("Movies in configuration file not yet supported") + elif method == "s": + if not config_update: + method = "show" + value = input("Enter Show (Name or Rating Key): ") + if value is int: + plex_show = plex_tools.get_show(int(value)) + print('+++ Adding %s to collection %s' % ( + plex_show.title, selected_collection.title)) + plex_show.addCollection(selected_collection.title) + else: + results = plex_tools.get_show(plex, value) + if len(results) > 1: + while True: + i = 1 + for result in results: + print("{POS}) {TITLE} - {RATINGKEY}".format(POS=i, TITLE=result.title, + RATINGKEY=result.ratingKey)) + i += 1 + s = input("Select show (N for None): ") + if int(s): + s = int(s) + if len(results) >= s > 0: + result = results[s - 1] + print('+++ Adding %s to collection %s' % ( + result.title, selected_collection.title)) + result.addCollection(selected_collection.title) + break + else: + break + else: + print("Shows in configuration file not yet supported") + elif method == "a": method = "actors" value = input("Enter Actor Name: ") @@ -87,11 +124,15 @@ def append_collection(config_update=None): if config_update: modify_config(collection_name, method, url) else: - missing = plex_tools.add_to_collection(plex, method, url, selected_collection.title) - if missing: - print("{} missing movies from IMDB List: {}".format(len(missing), url)) + missing_movies, missing_shows = plex_tools.add_to_collection(plex, method, url, selected_collection.title) + if missing_movies: + print("{} missing movies from {} List: {}".format(len(missing_movies), l_type, url)) if input("Add missing movies to Radarr? (y/n)").upper() == "Y": - add_to_radarr(missing) + add_to_radarr(missing_movies) + if missing_shows: + print("{} missing shows from {} List: {}".format(len(missing_shows), l_type, url)) + # if input("Add missing shows to Sonarr? (y/n)").upper() == "Y": + # add_to_sonarr(missing_shows) print("Bad {} List URL".format(l_type)) elif method == "c": @@ -196,12 +237,16 @@ def append_collection(config_update=None): c_name = input("Enter collection name: ") print("Processing {} List: {}".format(l_type, url)) try: - missing = plex_tools.add_to_collection(plex, method, url, c_name) - if missing: - print("{} missing movies from IMDB List: {}".format(len(missing), url)) + missing_movies, missing_shows = plex_tools.add_to_collection(plex, method, url, c_name) + if missing_movies: + print("{} missing items from {} List: {}".format(len(missing_movies), l_type, url)) if input("Add missing movies to Radarr? (y/n)").upper() == "Y": add_to_radarr(missing) - except (NameError, TypeError): + if missing_shows: + print("{} missing shows from {} List: {}".format(len(missing_shows), l_type, url)) + # if input("Add missing shows to Sonarr? (y/n)").upper() == "Y": + # add_to_sonarr(missing) + except (NameError, TypeError) as f: print("Bad {} list URL".format(l_type)) except KeyError as e: print(e) @@ -239,10 +284,10 @@ def append_collection(config_update=None): data = input("Enter collection name to search for (blank for all): ") collection = plex_tools.get_collection(plex, data) if not isinstance(collection, str): - print("Found collection {}".format(collection.title)) - movies = collection.children - print("Movies in collection: ") - for i, m in enumerate(movies): + print("Found {} collection {}".format(collection.subtype, collection.title)) + items = collection.children + print("{}s in collection: ".format(collection.subtype).capitalize()) + for i, m in enumerate(items): print("{}) {}".format(i + 1, m.title)) else: print(collection) diff --git a/plex_tools.py b/plex_tools.py index 0fd7376..48a17f3 100755 --- a/plex_tools.py +++ b/plex_tools.py @@ -1,6 +1,8 @@ from plexapi.video import Movie +from plexapi.video import Show from plexapi import exceptions as PlexExceptions import imdb_tools +import trakt_tools def get_movie(plex, data): @@ -20,6 +22,27 @@ def get_movie(plex, data): else: return "Movie: " + data + " not found" +def get_item(plex, data): + # If an int is passed as data, assume it is a movie's rating key + if isinstance(data, int): + try: + return plex.Server.fetchItem(data) + except PlexExceptions.BadRequest: + return "Nothing found" + elif isinstance(data, Movie): + return data + elif isinstance(data, Show): + return data + else: + print(data) + movie_list = plex.MovieLibrary.search(title=data) + show_list = plex.ShowLibrary.search(title=data) + item_list = movie_list + show_list + if item_list: + return item_list + else: + return "Item: " + data + " not found" + def get_actor_rkey(plex, data): """Takes in actors name as str and returns as Plex's corresponding rating key ID""" search = data @@ -51,15 +74,29 @@ def get_actor_rkey(plex, data): def get_all_movies(plex): return plex.MovieLibrary.all() -def get_collection(plex, data, exact=None): - collection_list = plex.MovieLibrary.search(title=data, libtype="collection") +def get_all_shows(plex): + return plex.ShowLibrary.all() + +def get_all_items(plex): + return plex.MovieLibrary.all() + plex.ShowLibrary.all() + +# subtype can be 'movie', 'show', or None (movie/tv combined) +def get_collection(plex, data, exact=None, subtype=None): + if subtype == 'movie': + collection_list = plex.MovieLibrary.search(title=data, libtype="collection") + elif subtype == 'show': + collection_list = plex.ShowLibrary.search(title=data, libtype="collection") + else: + collection_movie_list = plex.MovieLibrary.search(title=data, libtype="collection") + collection_show_list = plex.ShowLibrary.search(title=data, libtype="collection") + collection_list = list(set(collection_movie_list + collection_show_list)) if len(collection_list) > 1: if exact: for collection in collection_list: if collection.title == data: return collection else: - c_names = [(str(i + 1) + ") " + collection.title) for i, collection in enumerate(collection_list)] + c_names = [(str(i + 1) + ") " + collection.title + " (" + collection.subtype + ")") for i, collection in enumerate(collection_list)] print("\n".join(c_names)) while True: try: @@ -74,7 +111,8 @@ def get_collection(plex, data, exact=None): print("Invalid entry") elif len(collection_list) == 1: if exact: - if collection_list[0] == data: + # if collection_list[0] == data: + if collection_list[0].title == data: return collection_list[0] else: return "Collection not in Plex, please update from config first" @@ -84,6 +122,10 @@ def get_collection(plex, data, exact=None): return "No collection found" def add_to_collection(plex, method, value, c, subfilters=None): + movies = [] + missing_movies = [] + shows = [] + missing_shows = [] if method in Movie.__doc__ or hasattr(Movie, method): try: movies = plex.MovieLibrary.search(**{method: value}) @@ -92,13 +134,20 @@ def add_to_collection(plex, method, value, c, subfilters=None): if method[-1:] == "s": movies = plex.MovieLibrary.search(**{method[:-1]: value}) movies = [m.ratingKey for m in movies if movies] + # elif method in Show.__doc__ or hasattr(Show, method): + if method in Show.__doc__ or hasattr(Show, method): + try: + shows = plex.ShowLibrary.search(**{method: value}) + except PlexExceptions.BadRequest as e: + print(e) else: if method == "imdb-list": - movies, missing = imdb_tools.imdb_get_movies(plex, value) + movies, missing_movies = imdb_tools.imdb_get_movies(plex, value) elif method == "tmdb-list": - movies, missing = imdb_tools.tmdb_get_movies(plex, value) + movies, missing_movies = imdb_tools.tmdb_get_movies(plex, value) elif method == "trakt-list": - movies, missing = imdb_tools.trakt_get_movies(plex, value) + movies, missing_movies = trakt_tools.trakt_get_movies(plex, value) + shows, missing_shows = trakt_tools.trakt_get_shows(plex, value) if movies: # Check if already in collection cols = plex.MovieLibrary.search(title=c, libtype="collection") @@ -145,12 +194,61 @@ def add_to_collection(plex, method, value, c, subfilters=None): elif not subfilters: print("+++ Adding {} to collection: {}".format(current_m.title, c)) current_m.addCollection(c) + if shows: + # Check if already in collection + cols = plex.ShowLibrary.search(title=c, libtype="collection") + try: + fs = cols[0].children + except IndexError: + fs = [] + for rk in shows: + # current_s = get_show(plex, rk) + current_s = get_item(plex, rk) + current_s.reload() + if current_s in fs: + print("{} is already in collection: {}".format(current_s.title, c)) + elif subfilters: + match = True + for sf in subfilters: + method = sf[0] + terms = str(sf[1]).split(", ") + try: + show_attrs = getattr(current_s, method) + # If it returns a list, get the 'tag' attribute + # Otherwise, it's a string. Make it a list. + if isinstance(show_attrs, list) and "-" not in method: + show_attrs = [getattr(x, 'tag') for x in show_attrs] + else: + show_attrs = [str(show_attrs)] + except AttributeError as e: + print(e) + # for media in current_s.media: + # if method == "video-resolution": + # show_attrs = [media.videoResolution] + # for part in media.parts: + # if method == "audio-language": + # show_attrs = ([audio_stream.language for audio_stream in part.audioStreams()]) + # if method == "subtitle-language": + # show_attrs = ([subtitle_stream.language for subtitle_stream in part.subtitleStreams()]) + + # Get the intersection of the user's terms and movie's terms + # If it's empty, it's not a match + if not list(set(terms) & set(show_attrs)): + match = False + break + if match: + print("+++ Adding {} to collection {}".format(current_s.title, c)) + current_s.addCollection(c) + elif not subfilters: + print("+++ Adding {} to collection: {}".format(current_s.title, c)) + current_s.addCollection(c) try: - missing + missing_movies + missing_shows except UnboundLocalError: return else: - return missing + return missing_movies, missing_shows def delete_collection(data): confirm = input("{} selected. Confirm deletion (y/n):".format(data.title)) diff --git a/requirements.txt b/requirements.txt index b0e9b6e..122f411 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ requests flask git+git://github.com/pkkid/python-plexapi.git#egg=plexapi trakt.py +ruamel.yaml diff --git a/trakt_helpers.py b/trakt_helpers.py new file mode 100644 index 0000000..1ed96a7 --- /dev/null +++ b/trakt_helpers.py @@ -0,0 +1,47 @@ +from trakt import Trakt + +import json +import os +import six +import copy +import ruamel.yaml + +def authenticate(authorization=None): + + if authorization['access_token']: + # Test authorization + with Trakt.configuration.oauth.from_response(authorization, refresh=True): + if Trakt['users/settings']: + # Successful authorization + return authorization + + print('Navigate to: %s' % Trakt['oauth'].authorize_url('urn:ietf:wg:oauth:2.0:oob')) + + code = six.moves.input('Authorization code: ') + if not code: + exit(1) + + authorization = Trakt['oauth'].token(code, 'urn:ietf:wg:oauth:2.0:oob') + if not authorization: + exit(1) + + # print('Authorization: %r' % authorization) + return authorization + +def save_authorization(config_file, authorization): + ruamel.yaml.YAML().allow_duplicate_keys = True + from ruamel.yaml.util import load_yaml_guess_indent + config, ind, bsi = load_yaml_guess_indent(open(config_file)) + config['trakt']['authorization']['access_token'] = authorization['access_token'] + config['trakt']['authorization']['token_type'] = authorization['token_type'] + config['trakt']['authorization']['expires_in'] = authorization['expires_in'] + config['trakt']['authorization']['refresh_token'] = authorization['refresh_token'] + config['trakt']['authorization']['scope'] = authorization['scope'] + config['trakt']['authorization']['created_at'] = authorization['created_at'] + print('Saving authorization information to {}'.format(config_file)) + ruamel.yaml.round_trip_dump( + config, + open(config_file, 'w'), + indent=ind, + block_seq_indent=bsi + ) diff --git a/trakt_tools.py b/trakt_tools.py new file mode 100644 index 0000000..c913644 --- /dev/null +++ b/trakt_tools.py @@ -0,0 +1,107 @@ +import config_tools +from urllib.parse import urlparse +import plex_tools +import trakt + +def trakt_get_movies(plex, data): + config_tools.TraktClient() + trakt_url = data + if trakt_url[-1:] == " ": + trakt_url = trakt_url[:-1] + imdb_map = {} + trakt_list_path = urlparse(trakt_url).path + trakt_list_items = trakt.Trakt[trakt_list_path].items() + title_ids = [m.pk[1] for m in trakt_list_items if isinstance(m, trakt.objects.movie.Movie)] + + if title_ids: + for item in plex.MovieLibrary.all(): + guid = urlparse(item.guid) + item_type = guid.scheme.split('.')[-1] + if item_type == 'imdb': + imdb_id = guid.netloc + elif item_type == 'themoviedb': + tmdb_id = guid.netloc + # lookup can sometimes return a list + lookup = trakt.Trakt['search'].lookup(tmdb_id, 'tmdb', 'movie') + if isinstance(lookup, list): + imdb_id = trakt.Trakt['search'].lookup(tmdb_id, 'tmdb', 'movie')[0].get_key('imdb') + else: + imdb_id = trakt.Trakt['search'].lookup(tmdb_id, 'tmdb', 'movie').get_key('imdb') + else: + imdb_id = None + + if imdb_id and imdb_id in title_ids: + imdb_map[imdb_id] = item + else: + imdb_map[item.ratingKey] = item + + matched_imbd_movies = [] + missing_imdb_movies = [] + for imdb_id in title_ids: + movie = imdb_map.pop(imdb_id, None) + if movie: + matched_imbd_movies.append(plex.Server.fetchItem(movie.ratingKey)) + else: + missing_imdb_movies.append(imdb_id) + + return matched_imbd_movies, missing_imdb_movies + else: + # No movies + return None, None + +def trakt_get_shows(plex, data): + config_tools.TraktClient() + trakt_url = data + if trakt_url[-1:] == " ": + trakt_url = trakt_url[:-1] + tvdb_map = {} + trakt_list_path = urlparse(trakt_url).path + trakt_list_items = trakt.Trakt[trakt_list_path].items() + title_ids = [] + for m in trakt_list_items: + if isinstance(m, trakt.objects.show.Show): + if m.pk[1] not in title_ids: + title_ids.append(m.pk[1]) + elif isinstance(m, trakt.objects.season.Season): + if m.show.pk[1] not in title_ids: + title_ids.append(m.show.pk[1]) + elif isinstance(m, trakt.objects.episode.Episode): + if m.show.pk[1] not in title_ids: + title_ids.append(m.show.pk[1]) + + if title_ids: + for item in plex.ShowLibrary.all(): + guid = urlparse(item.guid) + item_type = guid.scheme.split('.')[-1] + # print('item_type', item, item_type) + if item_type == 'thetvdb': + tvdb_id = guid.netloc + elif item_type == 'themoviedb': + tmdb_id = guid.netloc + lookup = trakt.Trakt['search'].lookup(tmdb_id, 'tmdb', 'movie') + if isinstance(lookup, list): + tvdb_id = trakt.Trakt['search'].lookup(tmdb_id, 'tmdb', 'show')[0].get_key('tvdb') + else: + tvdb_id = trakt.Trakt['search'].lookup(tmdb_id, 'tmdb', 'show').get_key('tvdb') + else: + tvdb_id = None + + if tvdb_id and tvdb_id in title_ids: + tvdb_map[tvdb_id] = item + else: + tvdb_map[item.ratingKey] = item + + matched_tvdb_shows = [] + missing_tvdb_shows = [] + + for tvdb_id in title_ids: + show = tvdb_map.pop(tvdb_id, None) + if show: + matched_tvdb_shows.append(plex.Server.fetchItem(show.ratingKey)) + else: + missing_tvdb_shows.append(tvdb_id) + + return matched_tvdb_shows, missing_tvdb_shows + else: + # No shows + return None, None