JBOPS/reporting/server_compare.py
yosifkit 973adc0148
Scope seen, missing, and unique per media type
Also drop unused "dupes" list
2021-05-08 18:15:37 -07:00

285 lines
9.5 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Description: Comparing content between two or more Plex servers.
Creates .json file in script directory of server compared.
.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
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"
"""
from __future__ import print_function
from __future__ import unicode_literals
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 = []
server_lst = []
def find_things(server, media_type):
"""Get all items based on media type
Parameters
----------
server: Object
plexServerObject
media_type: list
['movie', 'show', ..]
Returns
-------
dictionary
{media_type:[plexObject, ..]}
"""
dict_tt = {name: [] for name in media_type}
print('Finding items from {}.'.format(server.friendlyName))
for section in server.library.sections():
if section.title not in IGNORE_LST and section.type in media_type:
for item in server.library.section(section.title).all():
dict_tt[section.type].append(server.fetchItem(item.ratingKey))
return dict_tt
def get_meta(meta):
"""Get metadata from Plex item.
Parameters
----------
meta: Object
plexObject
Returns
-------
dictionary
{
"genres": [],
"imdb": "tt5158522",
"rating": float,
"server": ["Plex Server Name"],
"title": "Title"
}
"""
thumb_url = '{}{}?X-Plex-Token={}'.format(
meta._server._baseurl, meta.thumb, meta._server._token)
meta_dict = {'title': meta.title,
'rating': meta.rating if
meta.rating is not None else 0.0,
'genres': [x.tag for x in meta.genres],
'server': [meta._server.friendlyName],
'thumb': [thumb_url]
}
if meta.guid:
# guid will return (com.plexapp.agents.imdb://tt4302938?lang=en)
# Agents will differ between servers.
agent = meta.guid
source_name = agent.split('://')[0].split('.')[-1]
source_id = agent.split('://')[1].split('?')[0]
meta_dict[source_name] = source_id
if meta.type == 'movie':
# For movies with same titles
meta_dict['title'] = u'{} ({})'.format(meta.title, meta.year)
return meta_dict
def org_diff(lst_dicts, media_type, main_server):
"""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: {..}
}
"""
diff_dict = {}
# todo-me pull posters from connected servers
for mtype in media_type:
meta_lst = []
seen = {}
missing = []
unique = []
print('...combining {}s'.format(mtype))
for server_lst in lst_dicts:
for item in server_lst[mtype]:
if mtype == 'movie':
title = u'{} ({})'.format(item.title, item.year)
else:
title = item.title
# Look for duplicate titles
if title not in seen:
seen[title] = 1
meta_lst.append(get_meta(item))
else:
# Duplicate found
if seen[title] >= 1:
# Go back through list to find original
for meta in meta_lst:
if meta['title'] == title:
# Append the duplicate server's name
meta['server'].append(item._server.friendlyName)
thumb_url = '{}{}?X-Plex-Token={}'.format(
item._server._baseurl, item.thumb, item._server._token)
meta['thumb'].append(thumb_url)
seen[title] += 1
# Sort item list by Plex rating
# Duplicates will use originals rating
meta_lst = sorted(meta_lst, key=lambda d: d['rating'], reverse=True)
diff_dict[mtype] = {'combined': {
'count': len(meta_lst),
'list': meta_lst}}
print('...finding {}s missing from {}'.format(
mtype, main_server))
for item in meta_lst:
# Main Server name is alone in items server list
if main_server not in item['server']:
missing.append(item)
# Main Server name is absent in items server list
elif main_server in item['server'] and len(item['server']) == 1:
unique.append(item)
diff_dict[mtype].update({'missing': {
'count': len(missing),
'list': missing}})
print('...finding {}s unique to {}'.format(
mtype, main_server))
diff_dict[mtype].update({'unique': {
'count': len(unique),
'list': unique}})
return diff_dict
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Comparing content between two or more Plex servers.",
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--server', required=True, choices=SERVER_DICT.keys(),
action='append', nargs='?', metavar='',
help='Choose servers to connect to and compare.\n'
'Choices: (%(choices)s)')
parser.add_argument('--media_type', choices=['movie', 'show', 'artist'],
nargs='+', metavar='', default=['movie', 'show'],
help='Choose media type(s) to compare.'
'\nDefault: (%(default)s)'
'\nChoices: (%(choices)s)')
# todo-me add media_type [x], library_ignore[], media filters (genre, etc.) []
opts = parser.parse_args()
combined_lst = []
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]]
# First server in args is main server.
main_server = server_compare.connect()
print('Connected to {} server.'.format(main_server.friendlyName))
for server in opts.server[1:]:
other_server = SERVER_DICT[server]
# Attempt to connect to other servers
try:
server_connected = other_server.connect()
print('Connected to {} server.'.format(
server_connected.friendlyName))
server_lst.append(server_connected)
except Exception as e:
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)
combined_lst.append(find_things(main_server, opts.media_type))
servers = [server.friendlyName for server in server_lst]
for connection in server_lst:
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)