2018-07-25 11:45:34 +00:00
|
|
|
#!/usr/bin/env python
|
2018-07-23 14:51:24 +00:00
|
|
|
"""
|
|
|
|
Description: Comparing content between two or more Plex servers.
|
|
|
|
Creates .json file in script directory of server compared.
|
2018-08-03 21:35:49 +00:00
|
|
|
.json file contents include:
|
|
|
|
items by media types (movie, show)
|
|
|
|
items found in server 1, server 2, etc
|
|
|
|
items unique to server 1
|
|
|
|
items missing from server 1
|
2018-07-23 14:51:24 +00:00
|
|
|
Author: Blacktwin
|
|
|
|
Requires: requests, plexapi
|
|
|
|
|
|
|
|
Example:
|
|
|
|
python find_diff_other_servers.py --server "My Plex Server" --server PlexServer2
|
|
|
|
python find_diff_other_servers.py --server "My Plex Server" --server PlexServer2 --server "Steven Plex"
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import requests
|
|
|
|
import json
|
|
|
|
import sys
|
|
|
|
from plexapi.server import PlexServer, CONFIG
|
|
|
|
|
|
|
|
TAUTULLI_URL = ''
|
|
|
|
TAUTULLI_APIKEY = ''
|
|
|
|
TAUTULLI_URL = CONFIG.data['auth'].get('tautulli_baseurl', TAUTULLI_URL)
|
|
|
|
TAUTULLI_APIKEY = CONFIG.data['auth'].get('tautulli_apikey', TAUTULLI_APIKEY)
|
|
|
|
|
|
|
|
PLEX_URL = ''
|
|
|
|
PLEX_TOKEN = ''
|
|
|
|
PLEX_URL = CONFIG.data['auth'].get('server_baseurl', PLEX_URL)
|
|
|
|
PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN)
|
|
|
|
|
|
|
|
# Sections to ignore from comparision.
|
|
|
|
IGNORE_LST = ['Library name']
|
|
|
|
|
|
|
|
sess = requests.Session()
|
|
|
|
# Ignore verifying the SSL certificate
|
|
|
|
sess.verify = False # '/path/to/certfile'
|
|
|
|
# If verify is set to a path to a directory,
|
|
|
|
# the directory must have been processed using the c_rehash utility supplied
|
|
|
|
# with OpenSSL.
|
|
|
|
if sess.verify is False:
|
|
|
|
# Disable the warning that the request is insecure, we know that...
|
|
|
|
import urllib3
|
|
|
|
|
|
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
|
|
|
|
|
|
plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess)
|
|
|
|
|
|
|
|
SERVER_DICT = {server.name: server for server in
|
|
|
|
plex.myPlexAccount().resources()
|
|
|
|
if 'server' in server.provides.split(',')}
|
|
|
|
|
|
|
|
shared_lst = []
|
2018-07-25 11:22:19 +00:00
|
|
|
server_lst = []
|
2018-07-23 14:51:24 +00:00
|
|
|
|
2018-07-29 19:11:02 +00:00
|
|
|
|
2018-07-26 19:05:35 +00:00
|
|
|
def find_things(server, media_type):
|
2018-08-03 21:35:49 +00:00
|
|
|
"""Get all items based on media type
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
server: Object
|
|
|
|
plexServerObject
|
|
|
|
media_type: list
|
|
|
|
['movie', 'show', ..]
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
dictionary
|
|
|
|
{media_type:[plexObject, ..]}
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2018-07-28 04:24:41 +00:00
|
|
|
dict_tt = {name: [] for name in media_type}
|
2018-07-23 14:51:24 +00:00
|
|
|
print('Finding items from {}.'.format(server.friendlyName))
|
|
|
|
for section in server.library.sections():
|
2018-07-26 19:05:35 +00:00
|
|
|
if section.title not in IGNORE_LST and section.type in media_type:
|
2018-07-23 14:51:24 +00:00
|
|
|
for item in server.library.section(section.title).all():
|
2018-07-24 13:00:18 +00:00
|
|
|
dict_tt[section.type].append(server.fetchItem(item.ratingKey))
|
2018-07-23 14:51:24 +00:00
|
|
|
|
|
|
|
return dict_tt
|
|
|
|
|
|
|
|
|
2018-07-31 12:34:28 +00:00
|
|
|
def get_meta(meta):
|
2018-08-03 21:35:49 +00:00
|
|
|
"""Get metadata from Plex item.
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
meta: Object
|
|
|
|
plexObject
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
dictionary
|
|
|
|
{
|
|
|
|
"genres": [],
|
|
|
|
"imdb": "tt5158522",
|
|
|
|
"rating": float,
|
|
|
|
"server": ["Plex Server Name"],
|
|
|
|
"title": "Title"
|
|
|
|
}
|
|
|
|
"""
|
2018-07-29 19:11:02 +00:00
|
|
|
|
2018-07-31 12:34:28 +00:00
|
|
|
meta_dict = {'title': meta.title,
|
2018-07-30 20:19:30 +00:00
|
|
|
'rating': meta.rating,
|
2018-07-31 12:34:28 +00:00
|
|
|
'genres': [x.tag for x in meta.genres],
|
|
|
|
'server': [meta._server.friendlyName]
|
2018-07-30 20:19:30 +00:00
|
|
|
}
|
2018-07-29 19:11:02 +00:00
|
|
|
if meta.guid:
|
2018-08-03 21:35:49 +00:00
|
|
|
# guid will return (com.plexapp.agents.imdb://tt4302938?lang=en)
|
|
|
|
# Agents will differ between servers.
|
2018-07-29 19:11:02 +00:00
|
|
|
agent = meta.guid
|
|
|
|
source_name = agent.split('://')[0].split('.')[-1]
|
|
|
|
source_id = agent.split('://')[1].split('?')[0]
|
|
|
|
meta_dict[source_name] = source_id
|
2018-07-27 15:54:17 +00:00
|
|
|
|
2018-08-02 19:53:25 +00:00
|
|
|
if meta.type == 'movie':
|
2018-08-03 21:35:49 +00:00
|
|
|
# For movies with same titles
|
2018-08-02 19:53:25 +00:00
|
|
|
meta_dict['title'] = u'{} ({})'.format(meta.title, meta.year)
|
2018-07-27 15:54:17 +00:00
|
|
|
return meta_dict
|
|
|
|
|
|
|
|
|
2018-10-08 03:59:33 +00:00
|
|
|
def org_diff(lst_dicts, media_type, main_server):
|
2018-08-03 21:35:49 +00:00
|
|
|
"""Organizing the items from each server
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
media_type: list
|
|
|
|
['movie', 'show',..]
|
|
|
|
lst_dicts: list
|
|
|
|
[{media_type:[plexObject, ..]}, {media_type: [..]}]
|
|
|
|
main_server: str
|
|
|
|
'Plex Server Name'
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
nested dictionary
|
|
|
|
media_type: {combined :{
|
|
|
|
list: [
|
|
|
|
{
|
|
|
|
"genres": [],
|
|
|
|
"imdb": "tt2313197",
|
|
|
|
"rating": float,
|
|
|
|
"server": ["Plex Server Name"],
|
|
|
|
"title": ""
|
|
|
|
},
|
|
|
|
],
|
|
|
|
count: int
|
|
|
|
},
|
|
|
|
missing: {..}
|
|
|
|
unique: {..}
|
|
|
|
}
|
|
|
|
"""
|
2018-07-25 20:09:00 +00:00
|
|
|
diff_dict = {}
|
2018-08-01 18:36:05 +00:00
|
|
|
seen = {}
|
|
|
|
dupes = []
|
|
|
|
missing = []
|
|
|
|
unique = []
|
2018-10-08 03:59:33 +00:00
|
|
|
# todo-me pull posters from connected servers
|
2018-08-01 18:36:05 +00:00
|
|
|
|
2018-07-31 12:34:28 +00:00
|
|
|
for mtype in media_type:
|
|
|
|
meta_lst = []
|
|
|
|
print('...combining {}s'.format(mtype))
|
2018-08-01 18:36:05 +00:00
|
|
|
for server_lst in lst_dicts:
|
|
|
|
for item in server_lst[mtype]:
|
2018-08-02 19:53:25 +00:00
|
|
|
if mtype == 'movie':
|
|
|
|
title = u'{} ({})'.format(item.title, item.year)
|
|
|
|
else:
|
|
|
|
title = item.title
|
2018-10-08 03:59:33 +00:00
|
|
|
|
2018-08-03 21:35:49 +00:00
|
|
|
# Look for duplicate titles
|
2018-08-02 19:53:25 +00:00
|
|
|
if title not in seen:
|
|
|
|
seen[title] = 1
|
2018-08-01 18:36:05 +00:00
|
|
|
meta_lst.append(get_meta(item))
|
|
|
|
else:
|
2018-08-03 21:35:49 +00:00
|
|
|
# Duplicate found
|
2018-08-02 19:53:25 +00:00
|
|
|
if seen[title] >= 1:
|
2018-10-08 03:59:33 +00:00
|
|
|
dupes.append([title, item._server.friendlyName])
|
2018-08-03 21:35:49 +00:00
|
|
|
# Go back through list to find original
|
2018-08-01 18:36:05 +00:00
|
|
|
for meta in meta_lst:
|
2018-08-02 19:53:25 +00:00
|
|
|
if meta['title'] == title:
|
2018-08-03 21:35:49 +00:00
|
|
|
# Append the duplicate server's name
|
2018-08-01 18:36:05 +00:00
|
|
|
meta['server'].append(item._server.friendlyName)
|
2018-08-02 19:53:25 +00:00
|
|
|
seen[title] += 1
|
2018-08-03 21:35:49 +00:00
|
|
|
# Sort item list by Plex rating
|
|
|
|
# Duplicates will use originals rating
|
2018-10-08 03:59:33 +00:00
|
|
|
meta_lst = sorted(meta_lst, key=lambda d: d['rating'], reverse=True)
|
2018-08-01 18:36:05 +00:00
|
|
|
diff_dict[mtype] = {'combined': {'count': len(meta_lst),
|
|
|
|
'list': meta_lst}}
|
|
|
|
|
2018-07-31 12:34:28 +00:00
|
|
|
print('...finding {}s missing from {}'.format(
|
|
|
|
mtype, main_server))
|
2018-08-01 18:36:05 +00:00
|
|
|
for item in meta_lst:
|
2018-08-03 21:35:49 +00:00
|
|
|
# Main Server name is alone in items server list
|
2018-08-01 18:36:05 +00:00
|
|
|
if main_server not in item['server']:
|
|
|
|
missing.append(item)
|
2018-08-03 21:35:49 +00:00
|
|
|
# Main Server name is absent in items server list
|
2018-08-01 18:36:05 +00:00
|
|
|
elif main_server in item['server'] and len(item['server']) == 1:
|
|
|
|
unique.append(item)
|
|
|
|
diff_dict[mtype].update({'missing': {'count': len(missing),
|
|
|
|
'list': missing}})
|
|
|
|
|
2018-07-31 12:44:21 +00:00
|
|
|
print('...finding {}s unique to {}'.format(
|
|
|
|
mtype, main_server))
|
|
|
|
diff_dict[mtype].update({'unique': {'count': len(unique),
|
|
|
|
'list': unique}})
|
2018-07-23 14:51:24 +00:00
|
|
|
|
2018-07-25 20:09:00 +00:00
|
|
|
return diff_dict
|
2018-07-23 14:51:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
2018-07-26 19:05:35 +00:00
|
|
|
description="Comparing content between two or more Plex servers.",
|
|
|
|
formatter_class = argparse.RawTextHelpFormatter)
|
2018-07-23 14:51:24 +00:00
|
|
|
parser.add_argument('--server', required=True, choices=SERVER_DICT.keys(),
|
|
|
|
action='append', nargs='?', metavar='',
|
2018-07-26 19:05:35 +00:00
|
|
|
help='Choose servers to connect to and compare.\n'
|
|
|
|
'Choices: (%(choices)s)')
|
2018-07-29 19:11:02 +00:00
|
|
|
parser.add_argument('--media_type', choices=['movie', 'show', 'artist'],
|
2018-07-26 19:05:35 +00:00
|
|
|
nargs='+', metavar='', default=['movie', 'show'],
|
|
|
|
help='Choose media type(s) to compare.'
|
|
|
|
'\nDefault: (%(default)s)'
|
2018-07-23 14:51:24 +00:00
|
|
|
'\nChoices: (%(choices)s)')
|
2018-07-26 19:05:35 +00:00
|
|
|
# todo-me add media_type [x], library_ignore[], media filters (genre, etc.) []
|
2018-07-23 14:51:24 +00:00
|
|
|
|
|
|
|
opts = parser.parse_args()
|
2018-07-31 12:34:28 +00:00
|
|
|
combined_lst = []
|
2018-07-23 14:51:24 +00:00
|
|
|
|
|
|
|
if len(opts.server) < 2:
|
|
|
|
sys.stderr.write("Need more than one server to compare.\n")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
server_compare = SERVER_DICT[opts.server[0]]
|
2018-08-03 21:35:49 +00:00
|
|
|
# First server in args is main server.
|
2018-07-23 14:51:24 +00:00
|
|
|
main_server = server_compare.connect()
|
|
|
|
print('Connected to {} server.'.format(main_server.friendlyName))
|
|
|
|
|
|
|
|
for server in opts.server[1:]:
|
|
|
|
other_server = SERVER_DICT[server]
|
2018-08-03 21:35:49 +00:00
|
|
|
# Attempt to connect to other servers
|
2018-07-23 14:51:24 +00:00
|
|
|
try:
|
|
|
|
server_connected = other_server.connect()
|
|
|
|
print('Connected to {} server.'.format(
|
|
|
|
server_connected.friendlyName))
|
2018-07-25 11:22:19 +00:00
|
|
|
server_lst.append(server_connected)
|
2018-07-23 14:51:24 +00:00
|
|
|
except Exception as e:
|
2018-07-25 11:22:19 +00:00
|
|
|
sys.stderr.write("Error: {}.\nSkipping...\n".format(e))
|
|
|
|
pass
|
|
|
|
|
|
|
|
if len(server_lst) == 0:
|
|
|
|
sys.stderr.write("Need more than one server to compare.\n")
|
|
|
|
sys.exit(1)
|
|
|
|
|
2018-07-31 12:34:28 +00:00
|
|
|
combined_lst.append(find_things(main_server, opts.media_type))
|
|
|
|
|
|
|
|
servers = [server.friendlyName for server in server_lst]
|
2018-07-23 14:51:24 +00:00
|
|
|
|
2018-07-25 11:22:19 +00:00
|
|
|
for connection in server_lst:
|
2018-07-31 12:34:28 +00:00
|
|
|
combined_lst.append(find_things(connection, opts.media_type))
|
|
|
|
|
|
|
|
print('Combining findings from {} and {}'.format(
|
|
|
|
main_server.friendlyName, ' and '.join(servers)))
|
|
|
|
|
|
|
|
main_dict = org_diff(combined_lst, opts.media_type, main_server.friendlyName)
|
|
|
|
|
|
|
|
filename = 'diff_{}_{}_servers.json'.format(opts.server[0],'_'.join(servers))
|
|
|
|
|
|
|
|
with open(filename, 'w') as fp:
|
|
|
|
json.dump(main_dict, fp, indent=4, sort_keys=True)
|