mirror of
https://github.com/mza921/Plex-Auto-Collections
synced 2024-11-14 08:17:16 +00:00
commit
This commit is contained in:
parent
1356d9c099
commit
55419e29bb
9 changed files with 667 additions and 0 deletions
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
101
README.md
Normal file
101
README.md
Normal file
|
@ -0,0 +1,101 @@
|
|||
# Plex Auto Collections
|
||||
Python 3 script/[standalone build]() that works off a configuration file to create/update Plex collection. Supports IMDB
|
||||
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.
|
||||
|
||||
When parsing IMDB 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.
|
||||
|
||||
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.
|
||||
|
||||
Thanks to [/u/deva5610](https://www.reddit.com/user/deva5610) for [IMDBList2PlexCollection](https://github.com/deva5610/IMDBList2PlexCollection) which prompted
|
||||
the idea for a configuration based collection manager.
|
||||
|
||||
Subfilters also allows for a little more granular selection of movies to add to a collection. Unlike regular filters, a
|
||||
movie must match at least one value from each subfilter to be added to a collection.
|
||||
|
||||
# Disclaimer
|
||||
I'm not a developer. In fact this my first project I've seen to "completion". I taught myself Python as I went along.
|
||||
Because of this there are likely many bugs.
|
||||
|
||||
# Configuration
|
||||
Modify the supplied config.yml.template file.
|
||||
|
||||
If you do not want it to have the option to submit movies that are missing from IMDB lists do not include the api-key
|
||||
for TMBd or radarr. A TMDb apikey is not required for regular operation.
|
||||
|
||||
In order to find your Plex token follow
|
||||
[this guide](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/).
|
||||
|
||||
Main filters allowed are actors, imdb-list as well as many attributes that can found in the [plexapi.video.Movie
|
||||
documentation](https://python-plexapi.readthedocs.io/en/latest/modules/video.html#plexapi.video.Movie). In addition
|
||||
subfilters for audio language, subtitle language and video-resolution have been created. Take note that the values for
|
||||
each must match what Plex has including special characters in order to match.
|
||||
|
||||
subfilters:
|
||||
video-resolution: 1080
|
||||
audio-language: Français
|
||||
subtitle-language: Englis
|
||||
|
||||
**Once complete it should look like**
|
||||
|
||||
collections:
|
||||
Collection Name:
|
||||
imdb-list: https://www.imdb.com/list/ls068177081/
|
||||
actors: Seth Rogen, Aaron Paul
|
||||
studio: Lionsgate
|
||||
subfilters:
|
||||
audio-language: English
|
||||
genres: Action, Crime, Comedy
|
||||
server:
|
||||
library: Movies
|
||||
token: ###################
|
||||
url: http://192.168.1.5:32400
|
||||
radarr:
|
||||
url: http://192.168.1.5:7878/radarr/
|
||||
token: ###########################
|
||||
quality_profile_id: 4
|
||||
tmdb:
|
||||
apikey: ############################
|
||||
|
||||
# Usage
|
||||
[Standalone binaries]() have been created for both Windows and Linux.
|
||||
|
||||
If you would like to run from Python I have only tested this fully on Python 3.7.4. Dependencies must be installed by running
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
If there are issues installing PyYAML 1.5.4 try
|
||||
|
||||
pip install -r requirements.txt --ignore-installed
|
||||
|
||||
Make sure that plexapi is installed from the github source in requirements.txt. The one provided by pip contains a bug
|
||||
that will cause certain movies to crash the script when processing IMDB lists. To ensure that you are running the of
|
||||
plexapi check utils.py contains the following:
|
||||
|
||||
######/plexapi/utils.py line 170
|
||||
def toDatetime(value, format=None):
|
||||
""" Returns a datetime object from the specified value.
|
||||
|
||||
Parameters:
|
||||
value (str): value to return as a datetime
|
||||
format (str): Format to pass strftime (optional; if value is a str).
|
||||
"""
|
||||
if value and value is not None:
|
||||
if format:
|
||||
value = datetime.strptime(value, format)
|
||||
else:
|
||||
value = datetime.fromtimestamp(int(value))
|
||||
return value
|
||||
|
||||
To run the script in a terminal run
|
||||
|
||||
python plex_auto_collections.py
|
||||
|
||||
If you would like to schedule the script to run on a schedule the script can be launched to automatically and only
|
||||
based off the collection and then quit by running. This applies to the standalones as well.
|
||||
|
||||
python plex_auto_collections.py --update
|
||||
|
91
config_tools.py
Executable file
91
config_tools.py
Executable file
|
@ -0,0 +1,91 @@
|
|||
import os
|
||||
import yaml
|
||||
from plex_tools import get_actor_rkey
|
||||
from plex_tools import add_to_collection
|
||||
from plexapi.server import PlexServer
|
||||
from plexapi.video import Movie
|
||||
from radarr_tools import add_to_radarr
|
||||
|
||||
class Config:
|
||||
def __init__(self):
|
||||
self.config_path = os.path.join(os.getcwd(), 'config.yml')
|
||||
self.data = yaml.load(open(self.config_path), Loader=yaml.FullLoader)
|
||||
self.plex = self.data['server']
|
||||
self.tmdb = self.data['tmdb']
|
||||
self.radarr = self.data['radarr']
|
||||
self.collections = self.data['collections']
|
||||
|
||||
class Plex:
|
||||
def __init__(self):
|
||||
config = Config().plex
|
||||
url = config['url']
|
||||
token = config['token']
|
||||
library = config['library']
|
||||
self.Server = PlexServer(url, token)
|
||||
self.MovieLibrary = self.Server.library.section(library)
|
||||
self.Movie = Movie
|
||||
|
||||
class Radarr:
|
||||
def __init__(self):
|
||||
config = Config().radarr
|
||||
self.url = config['url']
|
||||
self.token = config['token']
|
||||
self.quality = config['quality_profile_id']
|
||||
|
||||
class TMDB:
|
||||
def __init__(self):
|
||||
config = Config().tmdb
|
||||
self.apikey = config['apikey']
|
||||
|
||||
def update_from_config(plex, skip_radarr=False):
|
||||
collections = Config().collections
|
||||
for c in collections:
|
||||
print("Updating collection: {}...".format(c))
|
||||
methods = [m for m in collections[c] if "subfilters" not in m]
|
||||
if "subfilters" in collections[c]:
|
||||
subfilters = []
|
||||
for sf in collections[c]["subfilters"]:
|
||||
sf_string = sf, collections[c]["subfilters"][sf]
|
||||
subfilters.append(sf_string)
|
||||
for m in methods:
|
||||
values = collections[c][m].split(", ")
|
||||
for v in values:
|
||||
if m[-1:] == "s":
|
||||
m_print = m[:-1]
|
||||
else:
|
||||
m_print = m
|
||||
print("Processing {}: {}".format(m_print, v))
|
||||
if m == "actors" or m == "actor":
|
||||
v = get_actor_rkey(plex, v)
|
||||
try:
|
||||
missing = add_to_collection(plex, m, v, c, subfilters)
|
||||
except UnboundLocalError:
|
||||
missing = add_to_collection(plex, m, v, c)
|
||||
if missing:
|
||||
print("{} missing movies from IMDB List: {}".format(len(missing), v))
|
||||
if not skip_radarr:
|
||||
if input("Add missing movies to Radarr? (y/n)").upper() == "Y":
|
||||
add_to_radarr(missing)
|
||||
print("\n")
|
||||
|
||||
def modify_config(c_name, m, value):
|
||||
config = Config()
|
||||
if m == "movie":
|
||||
print("Movie's in config not supported yet")
|
||||
else:
|
||||
try:
|
||||
if value not in str(config.data['collections'][c_name][m]):
|
||||
try:
|
||||
config.data['collections'][c_name][m] = \
|
||||
config.data['collections'][c_name][m] + ", {}".format(value)
|
||||
except TypeError:
|
||||
config.data['collections'][c_name][m] = value
|
||||
else:
|
||||
print("Value already in collection config")
|
||||
return
|
||||
except KeyError:
|
||||
config.data['collections'][c_name][m] = value
|
||||
print("Updated config file")
|
||||
with open(config.config_path, "w") as f:
|
||||
yaml.dump(config.data, f)
|
||||
|
BIN
dist/plex_auto_collections
vendored
Executable file
BIN
dist/plex_auto_collections
vendored
Executable file
Binary file not shown.
50
imdb_tools.py
Executable file
50
imdb_tools.py
Executable file
|
@ -0,0 +1,50 @@
|
|||
import requests
|
||||
from lxml import html
|
||||
from tmdbv3api import TMDb
|
||||
from tmdbv3api import Movie
|
||||
import config_tools
|
||||
|
||||
|
||||
def imdb_get_movies(plex, data):
|
||||
tmdb = TMDb()
|
||||
movie = Movie()
|
||||
tmdb.api_key = config_tools.TMDB().apikey
|
||||
imdb_url = data
|
||||
if imdb_url[-1:] == " ":
|
||||
imdb_url = imdb_url[:-1]
|
||||
imdb_map = {}
|
||||
library_language = plex.MovieLibrary.language
|
||||
r = requests.get(imdb_url, headers={'Accept-Language': library_language})
|
||||
tree = html.fromstring(r.content)
|
||||
title_ids = tree.xpath("//div[contains(@class, 'lister-item-image')]"
|
||||
"//a/img//@data-tconst")
|
||||
for m in plex.MovieLibrary.all():
|
||||
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
|
||||
in_library_idx = []
|
||||
|
||||
matched_imbd_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))
|
||||
|
||||
# Get list of missing movies from selected list
|
||||
missing_imdb_movies = [imdb for idx, imdb in enumerate(title_ids)
|
||||
if idx not in in_library_idx]
|
||||
|
||||
return matched_imbd_movies, missing_imdb_movies
|
209
plex_auto_collections.py
Executable file
209
plex_auto_collections.py
Executable file
|
@ -0,0 +1,209 @@
|
|||
from config_tools import Plex
|
||||
from config_tools import update_from_config
|
||||
from config_tools import modify_config
|
||||
from config_tools import Config
|
||||
from plexapi.video import Movie
|
||||
import plex_tools
|
||||
from radarr_tools import add_to_radarr
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
plex = Plex()
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-u", "--update", help="Automatically update collections off config and quit",
|
||||
action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.update:
|
||||
update_from_config(plex, True)
|
||||
sys.exit(0)
|
||||
|
||||
print("===================================================================")
|
||||
print(" Plex Auto Collections by /u/iRawrz ")
|
||||
print("===================================================================")
|
||||
print("\n")
|
||||
|
||||
if hasattr(__builtins__, 'raw_input'):
|
||||
input = raw_input
|
||||
|
||||
if input("Update Collections from Config? (y/n): ").upper() == "Y":
|
||||
update_from_config(plex)
|
||||
|
||||
def append_collection(config_update=None):
|
||||
while True:
|
||||
if config_update:
|
||||
collection_name = config_update
|
||||
else:
|
||||
collection_name = input("Enter collection to add to: ")
|
||||
try:
|
||||
selected_collection = plex_tools.get_collection(plex, collection_name)
|
||||
if not isinstance(selected_collection, str):
|
||||
print("\"{}\" Selected.".format(selected_collection.title))
|
||||
finished = False
|
||||
while not finished:
|
||||
method = input("Add Movie(m), Actor(a), IMDB List(l), Custom (c)?: ")
|
||||
if method == "m":
|
||||
if not config_update:
|
||||
method = "movie"
|
||||
value = input("Enter Movie (Name or Rating Key): ")
|
||||
if value is int:
|
||||
plex_movie = plex_tools.get_movie(int(value))
|
||||
print('+++ Adding %s to collection %s' % (plex_movie.title, selected_collection.title))
|
||||
plex_movie.addCollection(selected_collection.title)
|
||||
else:
|
||||
results = plex_tools.get_movie(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 movie (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("Movies in configuration file not yet supported")
|
||||
|
||||
elif method == "a":
|
||||
method = "actors"
|
||||
value = input("Enter Actor Name: ")
|
||||
a_rkey = plex_tools.get_actor_rkey(plex, value)
|
||||
if config_update:
|
||||
modify_config(collection_name, method, value)
|
||||
else:
|
||||
plex_tools.add_to_collection(plex, method, a_rkey, selected_collection.title)
|
||||
|
||||
elif method == "l":
|
||||
method = "imdb-list"
|
||||
url = input("Enter IMDB List URL: ").strip()
|
||||
print("Processing IMDB List: {}".format(url))
|
||||
try:
|
||||
missing = plex_tools.add_to_collection(plex, "imdb-list", url, selected_collection.title)
|
||||
if missing:
|
||||
print("{} missing movies from IMDB List: {}".format(len(missing), url))
|
||||
if input("Add missing movies to Radarr? (y/n)").upper() == "Y":
|
||||
add_to_radarr(missing)
|
||||
except:
|
||||
print("Bad IMDB List URL")
|
||||
if config_update:
|
||||
modify_config(collection_name, method, url)
|
||||
|
||||
elif method == "c":
|
||||
print("Please read the below link to see valid filter types. "
|
||||
"Please note not all have been tested")
|
||||
print("https://python-plexapi.readthedocs.io/en/latest/modules/video.html?highlight=plexapi.video.Movie#plexapi.video.Movie")
|
||||
while True:
|
||||
method = input("Enter filter method: ")
|
||||
m_search = " " + method + " "
|
||||
if m_search in Movie.__doc__ or hasattr(Movie, m_search):
|
||||
if method[-1:] == "s":
|
||||
method_p = method[:-1]
|
||||
else:
|
||||
method_p = method
|
||||
value = input("Enter {}: ".format(method_p))
|
||||
if config_update:
|
||||
modify_config(collection_name, method, value)
|
||||
else:
|
||||
plex_tools.add_to_collection(plex, method, value, selected_collection.title)
|
||||
break
|
||||
else:
|
||||
print("Filter method did not match an attribute for plexapi.video.Movie")
|
||||
if input("Add more to collection? (y/n):") == "n":
|
||||
print("\n")
|
||||
finished = True
|
||||
break
|
||||
else:
|
||||
print(selected_collection)
|
||||
break
|
||||
except AttributeError:
|
||||
print("No collection found")
|
||||
|
||||
|
||||
mode = None
|
||||
while not mode == "q":
|
||||
try:
|
||||
print("Modes: Rescan (r), Actor(a), IMDB List(l), "
|
||||
"Add to Existing Collection (+), Delete(-), "
|
||||
"Search(s), Quit(q)")
|
||||
mode = input("Select Mode: ")
|
||||
|
||||
if mode == "a":
|
||||
actor = input("Enter actor name: ")
|
||||
a_rkey = plex_tools.get_actor_rkey(plex, actor)
|
||||
if isinstance(a_rkey, int):
|
||||
c_name = input("Enter collection name: ")
|
||||
plex_tools.add_to_collection(plex, "actors", a_rkey, c_name)
|
||||
else:
|
||||
print("Invalid actor")
|
||||
print("\n")
|
||||
|
||||
elif mode == "r":
|
||||
update_from_config(plex)
|
||||
|
||||
elif mode == "l":
|
||||
url = input("Enter IMDB List URL: ")
|
||||
c_name = input("Enter collection name: ")
|
||||
print("Processing IMDB List: {}".format(url))
|
||||
try:
|
||||
missing = plex_tools.add_to_collection(plex, "imdb-list", url, c_name)
|
||||
if missing:
|
||||
print("{} missing movies from IMDB List: {}".format(len(missing), url))
|
||||
if input("Add missing movies to Radarr? (y/n)").upper() == "Y":
|
||||
add_to_radarr(missing)
|
||||
except:
|
||||
print("Bad IMDB List URL")
|
||||
print("\n")
|
||||
|
||||
elif mode == "+":
|
||||
if input("Add to collection in config file? (y/n): ") == "y":
|
||||
collections = Config().collections
|
||||
for i, collection in enumerate(collections):
|
||||
print("{}) {}".format(i + 1, collection))
|
||||
selection = None
|
||||
while selection not in collections:
|
||||
selection = input("Enter Collection Number: ")
|
||||
try:
|
||||
if int(selection) > 0:
|
||||
selection = list(collections)[int(selection) - 1]
|
||||
else:
|
||||
print("Invalid selection")
|
||||
except (IndexError, ValueError) as e:
|
||||
print("Invalid selection")
|
||||
append_collection(selection)
|
||||
else:
|
||||
append_collection()
|
||||
|
||||
elif mode == "-":
|
||||
data = input("Enter collection name to search for (blank for all): ")
|
||||
collection = plex_tools.get_collection(plex, data)
|
||||
if not isinstance(collection, str):
|
||||
plex_tools.delete_collection(collection)
|
||||
else:
|
||||
print(collection)
|
||||
print("\n")
|
||||
|
||||
elif mode == "s":
|
||||
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("{}) {}".format(i + 1, m.title))
|
||||
else:
|
||||
print(collection)
|
||||
print("\n")
|
||||
except KeyboardInterrupt:
|
||||
print("\n")
|
||||
pass
|
146
plex_tools.py
Executable file
146
plex_tools.py
Executable file
|
@ -0,0 +1,146 @@
|
|||
from plexapi.video import Movie
|
||||
from plexapi import exceptions as PlexExceptions
|
||||
import imdb_tools
|
||||
|
||||
import inspect
|
||||
|
||||
|
||||
def get_movie(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
|
||||
else:
|
||||
print(data)
|
||||
movie_list = plex.MovieLibrary.search(title=data)
|
||||
if movie_list:
|
||||
return movie_list
|
||||
else:
|
||||
return "Movie: " + 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
|
||||
|
||||
# We must first perform standard search against the Plex Server
|
||||
# Searching in the Library via Actor only works if the ratingKey is already known
|
||||
results = plex.Server.search(search)
|
||||
for entry in results:
|
||||
entry = str(entry)
|
||||
entry = entry.split(":")
|
||||
entry[0] = entry[0][1:]
|
||||
if entry[0] == "Movie":
|
||||
movie_id = int(entry[1])
|
||||
break
|
||||
|
||||
try:
|
||||
# We need to pull details from a movie to correspond the actor's name to their Plex Rating Key
|
||||
movie_roles = plex.Server.fetchItem(movie_id).roles
|
||||
for role in movie_roles:
|
||||
role = str(role).split(":")
|
||||
movie_actor_id = role[1]
|
||||
movie_actor_name = role[2][:-1].upper()
|
||||
if search.upper().replace(" ", "-") == movie_actor_name:
|
||||
actor_id = movie_actor_id
|
||||
return int(actor_id)
|
||||
except UnboundLocalError:
|
||||
return "Actor: " + search + " not found"
|
||||
|
||||
def get_all_movies(plex):
|
||||
return plex.MovieLibrary.all()
|
||||
|
||||
def get_collection(plex, data):
|
||||
collection_list = plex.MovieLibrary.search(title=data, libtype="collection")
|
||||
if len(collection_list) > 1:
|
||||
c_names = [(str(i+1) + ") " + collection.title) for i, collection in enumerate(collection_list)]
|
||||
print("\n".join(c_names))
|
||||
while True:
|
||||
try:
|
||||
selection = int(input("Choose collection number: ")) - 1
|
||||
if selection >= 0:
|
||||
return collection_list[selection]
|
||||
elif selection == "q":
|
||||
return
|
||||
else:
|
||||
print("Invalid entry")
|
||||
except (IndexError, ValueError) as E:
|
||||
print("Invalid entry")
|
||||
elif len(collection_list) == 1:
|
||||
return collection_list[0]
|
||||
else:
|
||||
return "No collection found"
|
||||
|
||||
def add_to_collection(plex, method, value, c, subfilters=None):
|
||||
if method in Movie.__doc__ or hasattr(Movie, method):
|
||||
try:
|
||||
movies = plex.MovieLibrary.search(**{method: value})
|
||||
except PlexExceptions.BadRequest:
|
||||
# If last character is "s" remove it and try again
|
||||
if method[-1:] == "s":
|
||||
movies = plex.MovieLibrary.search(**{method[:-1]: value})
|
||||
movies = [m.ratingKey for m in movies if movies]
|
||||
else:
|
||||
if method == "imdb-list":
|
||||
movies, missing = imdb_tools.imdb_get_movies(plex, value)
|
||||
if movies:
|
||||
# Check if already in collection
|
||||
cols = plex.MovieLibrary.search(title=c, libtype="collection")
|
||||
try:
|
||||
fs = cols[0].children
|
||||
except IndexError:
|
||||
fs = []
|
||||
for rk in movies:
|
||||
current_m = get_movie(plex, rk)
|
||||
current_m.reload()
|
||||
if current_m in fs:
|
||||
print("{} is already in collection: {}".format(current_m.title, c))
|
||||
elif subfilters:
|
||||
match = True
|
||||
for sf in subfilters:
|
||||
method = sf[0]
|
||||
terms = str(sf[1]).split(", ")
|
||||
try:
|
||||
mv_attrs = getattr(current_m, method)
|
||||
# If it returns a list, get the 'tag' attribute
|
||||
# Otherwise, it's a string. Make it a list.
|
||||
if isinstance(mv_attrs, list) and "-" not in method:
|
||||
mv_attrs = [getattr(x, 'tag') for x in mv_attrs]
|
||||
else:
|
||||
mv_attrs = [str(mv_attrs)]
|
||||
except AttributeError:
|
||||
for media in current_m.media:
|
||||
for part in media.parts:
|
||||
if method == "audio-language":
|
||||
mv_attrs = ([audio_stream.language for audio_stream in part.audioStreams()])
|
||||
if method == "subtitle-language":
|
||||
mv_attrs = ([subtitle_stream for subtitle_stream in part.subtitleStreams()])
|
||||
if method == "video-resolution":
|
||||
mv_attrs = ([mv_part.videoResolution for mv_part in rk.media])
|
||||
|
||||
# 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(mv_attrs)):
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
print("+++ Adding {} to collection {}".format(current_m.title, c))
|
||||
current_m.addCollection(c)
|
||||
elif not subfilters:
|
||||
print("+++ Adding {} to collection: {}".format(current_m.title, c))
|
||||
current_m.addCollection(c)
|
||||
try:
|
||||
missing
|
||||
except UnboundLocalError:
|
||||
return
|
||||
else:
|
||||
return missing
|
||||
|
||||
def delete_collection(data):
|
||||
confirm = input("{} selected. Confirm deletion (y/n):".format(data.title))
|
||||
if confirm == "y":
|
||||
data.delete()
|
||||
print("Collection deleted")
|
65
radarr_tools.py
Executable file
65
radarr_tools.py
Executable file
|
@ -0,0 +1,65 @@
|
|||
import re, json, requests, os, yaml
|
||||
from tmdbv3api import TMDb
|
||||
from tmdbv3api import Movie
|
||||
|
||||
def add_to_radarr(missing):
|
||||
config_path = os.path.join(os.getcwd(), 'config.yml')
|
||||
config = yaml.load(open(config_path), Loader=yaml.FullLoader)
|
||||
|
||||
tmdb = TMDb()
|
||||
tmdb.api_key = config['tmdb']['apikey']
|
||||
tmdb.language = "en"
|
||||
|
||||
url = config['radarr']['url'] + "/api/movie"
|
||||
quality = config['radarr']['quality_profile_id']
|
||||
token = config['radarr']['token']
|
||||
querystring = {"apikey": "{}".format(token)}
|
||||
|
||||
if "None" in (tmdb.api_key, url, quality, token):
|
||||
print("\n")
|
||||
print("All TMDB / Radarr details must be filled out in the configuration "
|
||||
"file to import missing movies into Radarr")
|
||||
print("\n")
|
||||
return
|
||||
|
||||
movie = Movie()
|
||||
for m in missing:
|
||||
tmdb_details = movie.external(external_id=str(m), external_source="imdb_id")['movie_results'][0]
|
||||
|
||||
tmdb_title = tmdb_details['title']
|
||||
tmdb_year = tmdb_details['release_date'].split("-")[0]
|
||||
tmdb_id = tmdb_details['id']
|
||||
tmdb_poster = "https://image.tmdb.org/t/p/original{}".format(tmdb_details['poster_path'])
|
||||
|
||||
titleslug = "{} {}".format(tmdb_title, tmdb_year)
|
||||
titleslug = re.sub(r'([^\s\w]|_)+', '', titleslug)
|
||||
titleslug = titleslug.replace(" ", "-")
|
||||
titleslug = titleslug.lower()
|
||||
|
||||
payload = {
|
||||
"title": tmdb_title,
|
||||
"qualityProfileId": quality,
|
||||
"year": int(tmdb_year),
|
||||
"tmdbid": str(tmdb_id),
|
||||
"titleslug": titleslug,
|
||||
"monitored": "true",
|
||||
"rootFolderPath": "//mnt//user//PlexMedia//movies",
|
||||
"images": [{
|
||||
"covertype": "poster",
|
||||
"url": tmdb_poster
|
||||
}]
|
||||
}
|
||||
headers = {
|
||||
'Content-Type': "application/json",
|
||||
'cache-control': "no-cache",
|
||||
'Postman-Token': "0eddcc07-12ba-49d3-9756-3aa8256deaf3"
|
||||
}
|
||||
|
||||
response = requests.request("POST", url, data=json.dumps(payload), headers=headers, params=querystring)
|
||||
r_json = json.loads(response.text)
|
||||
|
||||
try:
|
||||
if r_json[0]['errorMessage'] == "This movie has already been added":
|
||||
print(tmdb_title + " already added to Radarr")
|
||||
except KeyError:
|
||||
print("+++ " + tmdb_title + " added to Radarr")
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
PyYAML==5.1.2
|
||||
tmdbv3api
|
||||
lxml
|
||||
requests
|
||||
git+git://github.com/pkkid/python-plexapi.git#egg=plexapi
|
Loading…
Reference in a new issue