-
-
Save dirtycajunrice/efdffd23faf522e680dedfbba8a7aedf to your computer and use it in GitHub Desktop.
Updates all metadata in the Tautulli database after moving Plex libraries.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
# | |
# Description: Updates all metadata in the Tautulli database after moving Plex libraries. | |
# Author: /u/SwiftPanda16 | |
# Requires: plexapi, requests | |
from plexapi.server import PlexServer, CONFIG | |
import requests | |
### EDIT SETTINGS ### | |
## Install the required modules listed above with: | |
## python -m pip install plexapi | |
## python -m pip install requests | |
## NOTE: Only works with movie and tv show libraries. | |
## NOTE: Script requires 'api_sql = 1' to be enabled in the Tautulli config.ini file. | |
## Tautulli must be shut down before editing the config file. | |
TAUTULLI_URL = '' | |
TAUTULLI_APIKEY = '' | |
PLEX_URL = '' | |
PLEX_TOKEN = '' | |
if not TAUTULLI_URL: | |
TAUTULLI_URL = CONFIG.data['auth'].get('tautulli_baseurl', '') | |
if not TAUTULLI_APIKEY: | |
TAUTULLI_APIKEY = CONFIG.data['auth'].get('tautulli_apikey', '') | |
if not PLEX_URL: | |
PLEX_URL = CONFIG.data['auth'].get('server_baseurl', '') | |
if not PLEX_TOKEN: | |
PLEX_TOKEN = CONFIG.data['auth'].get('server_token', '') | |
if not all([TAUTULLI_URL, TAUTULLI_APIKEY, PLEX_URL, PLEX_TOKEN]): | |
print("missing required variable") | |
exit(1) | |
FALLBACK_MATCH_TITLE_YEAR = True # True or False, fallback to matching by title and year if matching by ID fails | |
FALLBACK_MATCH_TITLE = False # True or False, fallback to matching by title ONLY if matching by title and year fails | |
DRY_RUN = True # True to dry run without making changes to the Tautulli database, False to make changes | |
## CODE BELOW ## | |
def get_id_from_guid(guid): | |
id = None | |
if 'imdb://' in guid: | |
id = 'imdb://' + guid.split('imdb://')[1].split('?')[0] | |
elif 'themoviedb://' in guid: | |
id = 'themoviedb://' + guid.split('themoviedb://')[1].split('?')[0] | |
elif 'thetvdb://' in guid: | |
id = 'thetvdb://' + guid.split('thetvdb://')[1].split('?')[0].split('/')[0] | |
elif 'thetvdbdvdorder://' in guid: | |
id = 'thetvdbdvdorder://' + guid.split('thetvdbdvdorder://')[1].split('?')[0].split('/')[0] | |
elif 'plex://' in guid: | |
id = 'plex://' + guid.split('plex://')[1] | |
return id | |
def main(): | |
new_key_map = {} | |
old_key_map = {} | |
# Check for DRY_RUN. Backup Tautulli database if needed. | |
if DRY_RUN: | |
print("Dry run enabled. No changes will be made to the Tautulli database.") | |
else: | |
print("Not dry run. Creating a backup of the Tautulli database.") | |
params = {'cmd': 'backup_db', | |
'apikey': TAUTULLI_APIKEY, | |
} | |
requests.post(TAUTULLI_URL.rstrip('/') + '/api/v2', params=params) | |
# Get all old items from the Tautulli database (using raw SQL) | |
print("Retrieving all history items from the Tautulli database...") | |
params = {'cmd': 'sql', | |
'apikey': TAUTULLI_APIKEY, | |
'query': 'SELECT rating_key, grandparent_rating_key, title, grandparent_title, year, media_type, guid FROM session_history_metadata;' | |
} | |
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=params).json() | |
if r['response']['result'] == 'error': | |
print("Error retrieving Tautulli history: {}".format(r['response']['message'])) | |
print("Exiting script...") | |
return | |
else: | |
for row in r['response']['data']: | |
if row['media_type'] not in ('movie', 'episode'): | |
continue | |
id = get_id_from_guid(row['guid']) | |
if id: | |
key = row['grandparent_rating_key'] or row['rating_key'] | |
media_type = 'show' if row['media_type'] == 'episode' else row['media_type'] | |
title = row['grandparent_title'] or row['title'] | |
year = str(row['year']) | |
old_key_map[id] = (key, media_type, title, year) | |
#print(id, key, title) | |
else: | |
title = row['grandparent_title'] or row['title'] | |
print("\tUnsupported guid for '{title}' in the Tautulli database [Guid: {guid}]. Skipping...".format(title=title, guid=row['guid'])) | |
# Get all new items from the Plex server | |
print("Retrieving all library items from the Plex server...") | |
plex = PlexServer(PLEX_URL, PLEX_TOKEN) | |
for library in plex.library.sections(): | |
if library.type not in ('movie', 'show') or library.agent == 'com.plexapp.agents.none': | |
print("\tSkipping library: {title}".format(title=library.title)) | |
continue | |
print("\tScanning library: {title}".format(title=library.title)) | |
for item in library.all(): | |
id = get_id_from_guid(item.guid) | |
if id: | |
new_key_map[id] = (item.ratingKey, item.type, item.title, str(item.year)) | |
else: | |
print("\t\tUnsupported guid for '{title}' in the Plex library [Guid: {guid}]. Skipping...".format(title=item.title, guid=item.guid)) | |
new_title_year_map = {(title, year): (id, key, media_type) for id, (key, media_type, title, year) in new_key_map.items()} | |
new_title_map = {title: (id, key, media_type, year) for id, (key, media_type, title, year) in new_key_map.items()} | |
# Update metadata in the Tautulli database | |
print("{}Matching Tautulli items with Plex items...".format("(DRY RUN) " if DRY_RUN else "")) | |
if FALLBACK_MATCH_TITLE_YEAR: | |
print("\tUsing fallback to match by title and year.") | |
if FALLBACK_MATCH_TITLE: | |
print("\tUsing fallback to match by title ONLY.") | |
if not FALLBACK_MATCH_TITLE_YEAR and not FALLBACK_MATCH_TITLE: | |
print("\tNot using any fallback to title or year.") | |
updated = [] | |
no_mapping = [] | |
for id, (old_rating_key, old_type, title, year) in old_key_map.items(): | |
new_rating_key, new_type, _, _ = new_key_map.get(id, (None, None, None, None)) | |
new_year = None | |
warning_year = False | |
if not new_rating_key and FALLBACK_MATCH_TITLE_YEAR: | |
_, new_rating_key, new_type = new_title_year_map.get((title, year), (None, None, None)) | |
if not new_rating_key and FALLBACK_MATCH_TITLE: | |
_, new_rating_key, new_type, new_year = new_title_map.get(title, (None, None, None, None)) | |
if new_rating_key: | |
if new_rating_key != old_rating_key and new_type == old_type: | |
if new_year is not None and new_year != year: | |
warning_year = True | |
updated.append((title, year, old_rating_key, new_rating_key, new_type, new_year, warning_year)) | |
else: | |
no_mapping.append((title, year, old_rating_key)) | |
if updated: | |
if not DRY_RUN: | |
session = requests.Session() | |
url = TAUTULLI_URL + '/api/v2' | |
for title, year, old_rating_key, new_rating_key, new_type, new_year, warning_year in updated: | |
params = {'cmd': 'update_metadata_details', | |
'apikey': TAUTULLI_APIKEY, | |
'old_rating_key': old_rating_key, | |
'new_rating_key': new_rating_key, | |
'media_type': new_type} | |
session.post(url, params=params) | |
print("{}Updated metadata for {} items:".format("(DRY RUN) " if DRY_RUN else "", len(updated))) | |
for title, year, old_rating_key, new_rating_key, new_type, new_year, warning_year in updated: | |
if warning_year: | |
print("\t{title} ({year} --> {new_year}) [Rating Key: {old} --> {new}]".format( | |
title=title, year=year, new_year=new_year, old=old_rating_key, new=new_rating_key)) | |
else: | |
print("\t{title} ({year}) [Rating Key: {old} --> {new}]".format( | |
title=title, year=year, old=old_rating_key, new=new_rating_key)) | |
if no_mapping: | |
print("{}No match found for {} Tautulli items on the Plex server:".format("(DRY RUN) " if DRY_RUN else "", len(no_mapping))) | |
for title, year, old_rating_key in no_mapping: | |
print("\t{title} ({year}) [Rating Key: {old}]".format( | |
title=title, year=year, old=old_rating_key)) | |
# Clear all recently added items in the Tautulli database | |
print("{}Clearing all recently added items in the Tautulli database...".format("(DRY RUN) " if DRY_RUN else "")) | |
if not DRY_RUN: | |
params = {'cmd': 'sql', | |
'apikey': TAUTULLI_APIKEY, | |
'query': 'DELETE FROM recently_added;' | |
} | |
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=params).json() | |
if r['response']['result'] == 'error': | |
print("Error clearing the Tautulli recently added database table: {}".format(r['response']['message'])) | |
print("Exiting script...") | |
return | |
print("Cleared all items from the Tautulli recently added database table.") | |
if __name__ == "__main__": | |
main() | |
print("Done.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment