Skip to content

Instantly share code, notes, and snippets.

@JonnyWong16
Last active July 16, 2024 21:11
Show Gist options
  • Save JonnyWong16/b0e6b2761f8649d811f51866e682464b to your computer and use it in GitHub Desktop.
Save JonnyWong16/b0e6b2761f8649d811f51866e682464b to your computer and use it in GitHub Desktop.
Selects the default TMDB poster for movies in a Plex library if the current poster is from Gracenote.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Description: Selects the default TMDB poster if no poster is selected
or the current poster is from Gracenote.
Author: /u/SwiftPanda16
Requires: plexapi
Usage:
* Change the posters for an entire library:
python select_tmdb_poster.py --library "Movies"
* Change the poster for a specific item:
python select_tmdb_poster.py --rating_key 1234
* By default locked posters are skipped. To update locked posters:
python select_tmdb_poster.py --library "Movies" --include_locked
Tautulli script trigger:
* Notify on recently added
Tautulli script conditions:
* Filter which media to select the poster. Examples:
[ Media Type | is | movie ]
Tautulli script arguments:
* Recently Added:
--rating_key {rating_key}
'''
import argparse
import os
import plexapi.base
from plexapi.server import PlexServer
plexapi.base.USER_DONT_RELOAD_FOR_KEYS.add('fields')
# ## OVERRIDES - ONLY EDIT IF RUNNING SCRIPT WITHOUT TAUTULLI ##
PLEX_URL = ''
PLEX_TOKEN = ''
# Environmental Variables
PLEX_URL = PLEX_URL or os.getenv('PLEX_URL', PLEX_URL)
PLEX_TOKEN = PLEX_TOKEN or os.getenv('PLEX_TOKEN', PLEX_TOKEN)
def select_tmdb_poster_library(library, include_locked=False):
for item in library.all(includeGuids=False):
# Only reload for fields
item.reload(**{k: 0 for k, v in item._INCLUDES.items()})
select_tmdb_poster_item(item, include_locked=include_locked)
def select_tmdb_poster_item(item, include_locked=False):
# if item.isLocked('thumb') and not include_locked: # PlexAPI 4.5.10
if next((f.locked for f in item.fields if f.name == 'thumb'), False) and not include_locked:
print(f"Locked poster for {item.title}. Skipping.")
return
posters = item.posters()
selected_poster = next((p for p in posters if p.selected), None)
if selected_poster is None:
print(f"WARNING: No poster selected for {item.title}.")
else:
skipping = ' Skipping.' if selected_poster.provider != 'gracenote' else ''
print(f"Poster provider is '{selected_poster.provider}' for {item.title}.{skipping}")
if posters and (selected_poster is None or selected_poster.provider == 'gracenote'):
# Fallback to first poster if no TMDB posters are available
tmdb_poster = next((p for p in posters if p.provider == 'tmdb'), posters[0])
# Selecting the poster automatically locks it
tmdb_poster.select()
print(f"Selected {tmdb_poster.provider} poster for {item.title}.")
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--rating_key', type=int)
parser.add_argument('--library')
parser.add_argument('--include_locked', action='store_true')
opts = parser.parse_args()
plex = PlexServer(PLEX_URL, PLEX_TOKEN)
if opts.rating_key:
item = plex.fetchItem(opts.rating_key)
select_tmdb_poster_item(item, opts.include_locked)
elif opts.library:
library = plex.library.section(opts.library)
select_tmdb_poster_library(library, opts.include_locked)
else:
print("No --rating_key or --library specified. Exiting.")
@JonnyWong16
Copy link
Author

@pettykrooks
Copy link

Thanks, but I don’t understand how to set the parameters under the “usage” section. Do I edit the script or does it need to passed through from tortelli ? Thanks for your help

@pettykrooks
Copy link

this is all on a Mac but hopefully it helps someone:

gave up tortelli as I couldn't get it to run a python script without failing for some reason. in the end I got this script working using the 'python' command on my Mac. Macs don't come with python as its deprecated now so you're supposed to use 'python3' but that kept failing so I had to follow this to make the 'python' command work: https://ahmadawais.com/python-not-found-on-macos-install-python-with-brew-fix-path/

then I edited this bit of your script:

## OVERRIDES - ONLY EDIT IF RUNNING SCRIPT WITHOUT TAUTULLI

PLEX_URL = 'http://127.0.0.1:32400' had to use this I'm not sure why it wasn't the local ip for the server
PLEX_TOKEN = 'my token' (https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/)

then I could run it with one of the python commands:

  • Change the posters for an entire library:
    python select_tmdb_poster.py --library "Movies"
    • Change the poster for a specific item:
      python select_tmdb_poster.py --rating_key 1234
    • By default locked posters are skipped. To update locked posters:
      python select_tmdb_poster.py --library "Movies" --include_locked

thanks. and now I know some small amount of python

@KronK0321
Copy link

KronK0321 commented Jun 7, 2024

Hey, I ran into the following issue when trying to set this up to be triggered from Tautulli

Tautulli Notifiers :: Script error:
    Traceback (most recent call last):
        File "/home/jordan/select_tmdb_poster.py", line 90, in <module>
            select_tmdb_poster_library(library, opts.include_locked)
        File "/home/jordan/select_tmdb_poster.py", line 50, in select_tmdb_poster_library
            select_tmdb_poster_item(item, include_locked=include_locked)
        File "/home/jordan/select_tmdb_poster.py", line 72, in select_tmdb_poster_item
            tmdb_poster.select()
        File "/opt/Tautulli/lib/plexapi/media.py", line 1026, in select
            self._server.query(data, method=self._server._session.put)
        File "/opt/Tautulli/lib/plexapi/server.py", line 770, in query
            data = response.text.encode('utf8')
        File "/opt/Tautulli/lib/requests/models.py", line 928, in text
            encoding = self.apparent_encoding
        File "/opt/Tautulli/lib/requests/models.py", line 793, in apparent_encoding
            return chardet.detect(self.content)["encoding"]
    AttributeError: module 'chardet' has no attribute 'detect' 

I tracked it down to /opt/Tautulli/lib/requests/compat.py which substitutes 'charset_normalizer as chardet' if a try fails.

In my test that only contains one line

print(chardet.__dir__())

Tautulli logs show

Tautulli Notifiers :: Script returned:
    ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__path__'] 

If I rename /opt/Tautulli/lib/charset_normalizer, the output is:

Tautulli Notifiers :: Script returned:
    ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'List', 'Union', 'enums', 'charsetprober', 'charsetgroupprober', 'CharSetGroupProber', 'CharSetProber', 'InputState', 'resultdict', 'ResultDict', 'codingstatemachinedict', 'codingstatemachine', 'escsm', 'escprober', 'latin1prober', 'macromanprober', 'big5freq', 'euckrfreq', 'euctwfreq', 'gb2312freq', 'jisfreq', 'johabfreq', 'chardistribution', 'mbcharsetprober', 'mbcssm', 'big5prober', 'cp949prober', 'jpcntx', 'eucjpprober', 'euckrprober', 'euctwprober', 'gb2312prober', 'johabprober', 'sjisprober', 'utf8prober', 'mbcsgroupprober', 'sbcharsetprober', 'hebrewprober', 'langbulgarianmodel', 'langgreekmodel', 'langhebrewmodel', 'langrussianmodel', 'langthaimodel', 'langturkishmodel', 'sbcsgroupprober', 'utf1632prober', 'universaldetector', 'UniversalDetector', 'version', 'VERSION', '__version__', '__all__', 'detect', 'detect_all'] 

I'm running Python 3.10.12 and Tautulli v2.14.2

@thesil3nce
Copy link

Hello, I'm having trouble with the script notifier if you can help. It tries to start, but gives this error after playing
Tautulli Notifiers :: Script returned:     
No --rating_key or --library specified. Exiting.

@Healzangels
Copy link

Greetings!
Thanks for sharing just an awesome script! Has saved me a world of headache.
One thing I have noticed however is when I do a --library "Movies" within Tautulli everything works great, see it processing content however; not sure exactly when but the script will stop running leaving the later half of my collection unchanged.

Wondering if there is away to make sure the script continues to run until everything has been touched or maybe I'm just overlooking something simple. Thanks again

-Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment