Skip to content

Instantly share code, notes, and snippets.

@JonnyWong16
Last active April 28, 2024 23:52
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • 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 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.")
@ronaldheft
Copy link

I love the idea behind this script, but I'm getting the following error. Any ideas?

Tautulli Notifiers :: Script error:
    Traceback (most recent call last):
        File "/opt/Tautulli/scripts/select_tmdb_poster.py", line 106, in <module>
            select_tmdb_poster_item(item, opts.include_locked)
        File "/opt/Tautulli/scripts/select_tmdb_poster.py", line 74, in select_tmdb_poster_item
            if item.isLocked('thumb') and not include_locked:
        File "/opt/Tautulli/lib/plexapi/base.py", line 516, in __getattribute__
            value = super(PlexPartialObject, self).__getattribute__(attr)
    AttributeError: 'Movie' object has no attribute 'isLocked'

@JonnyWong16
Copy link
Author

@joe-eklund
Copy link

Hey, awesome code! Thanks!

I tried running it and was successful with a single movie, like this:

(plex-env) joe@sauron:~/utils/plex-scripts$ python select_tmdb_poster.py --rating_key 111171
Poster provider is 'gracenote' for The Card Counter.
Selected tmdb poster for The Card Counter.

I verified the poster was changed.

When I tried to run it on my library I get this error:

(plex-env) joe@sauron:~/utils/plex-scripts$ python select_tmdb_poster.py --library "Movies"
Traceback (most recent call last):
  File "/home/joe/utils/plex-scripts/select_tmdb_poster.py", line 86, in <module>
    select_tmdb_poster_library(library, opts.include_locked)
  File "/home/joe/utils/plex-scripts/select_tmdb_poster.py", line 46, in select_tmdb_poster_library
    item.reload(**{k: 0 for k, v in movie._INDLUCES.items()})
                                    ^^^^^
NameError: name 'movie' is not defined

Any ideas?

Using

  • Python 3.11.8
  • plexapi 4.15.10
  • PMS 1.32.8.7639.

@JonnyWong16
Copy link
Author

Typo. Fixed.

@joe-eklund
Copy link

Looks like that fixed that particular error. I am now getting this error:

(plex-env) joe@sauron:~/utils/plex-scripts$ python select_tmdb_poster.py --library "Movies"
Traceback (most recent call last):
  File "/home/joe/utils/plex-scripts/select_tmdb_poster.py", line 86, in <module>
    select_tmdb_poster_library(library, opts.include_locked)
  File "/home/joe/utils/plex-scripts/select_tmdb_poster.py", line 46, in select_tmdb_poster_library
    item.reload(**{k: 0 for k, v in item._INDLUCES.items()})
                                    ^^^^^^^^^^^^^^
  File "/home/joe/.pyenv/versions/plex-env/lib/python3.11/site-packages/plexapi/base.py", line 517, in __getattribute__
    value = super(PlexPartialObject, self).__getattribute__(attr)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'Movie' object has no attribute '_INDLUCES'. Did you mean: '_INCLUDES'?

@JonnyWong16
Copy link
Author

More typos.

@TheChrisK
Copy link

TheChrisK commented Mar 6, 2024

I'm working on getting this up and running and I am also getting

Tautulli Notifiers :: Script error:
    Traceback (most recent call last):
        File "/config/scripts/select_tmdb_poster.py", line 86, in <module>
            select_tmdb_poster_item(item, opts.include_locked)
        File "/config/scripts/select_tmdb_poster.py", line 54, in select_tmdb_poster_item
            if item.isLocked('thumb') and not include_locked:
                  ^^^^^^^^^^^^^
        File "/app/tautulli/lib/plexapi/base.py", line 516, in __getattribute__
            value = super(PlexPartialObject, self).__getattribute__(attr)
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    AttributeError: 'Movie' object has no attribute 'isLocked' 

Running it both in Tautulli and locally, same results. Latest version. The film I am running it on has not been touched since importing.

Adding the below to line 74 also throws an error:

if 'thumb' in [f.name for f in item.fields] and not include_locked:

Python version: 3.11.8
PlexAPI version: 4.15.10
Plex version: 1.40.0.7998

@JonnyWong16
Copy link
Author

Your PlexAPI version is out-of-date.

Tautulli issue already answered above.

@TheChrisK
Copy link

TheChrisK commented Mar 6, 2024

I updated to 4.15.10 realizing it was out of date (erroneously ran the update thinking it was good). It works now locally but not in Tautulli.

I saw the plexapi version wasn't yet updated in Tautulli, is that still the case?

Switched to the nightly branch and it works great! Thank you so much. This Gracenote change is just terrible.

@leonard4546
Copy link

is it possible to do it for tv shows and season posters?

@JonnyWong16
Copy link
Author

Change line 47 to:

    for item in library.all(libtype='season', includeGuids=False):

But why? There are no Gracenote posters for seasons so the script will just skip them anyways.

@joe-eklund
Copy link

So one problem I have is that Plex continues to change more and more posters as time passes. This is because I have my library set to periodically refresh metadata. One solution would be to turn that off, but I actually do want metadata to be periodically refreshed, just not my posters.

Is there a way to lock the posters that this script already detects as tmdb, instead of just skipping them. Otherwise I will have to periodically run this script to fix those posters that got changed in the metadata refresh.

@JonnyWong16
Copy link
Author

Insert at line 67:

        if skipping:
            item.lockPoster()

@johnloopi
Copy link

johnloopi commented Apr 2, 2024

Change line 47 to:

    for item in library.all(libtype='season', includeGuids=False):

But why? There are no Gracenote posters for seasons so the script will just skip them anyways.

Does Gracenote do Backdrops (or Backgrounds, depending on if you're on TMDB or in Plex) as well, or is it just the Poster part of the movies?

EDIT 1: Asking because Plex-Meta-Manager has options to either "mass_poster_update - Updates the poster of every item in the library." or "mass_background_update - Updates the background of every item in the library."¹ and some of my movie posters, E.g. Aftersun (2022) has a background that is far down the list of selectable backgrounds and looks just like the poster that is selected from Gracenote.

¹https://metamanager.wiki/en/latest/config/operations/#operation-attributes

EDIT 2:
You also have a script called "save_posters.py" and here (https://old.reddit.com/r/PleX/comments/1b3bba6/linux_python_script_to_dl_movie_posters_in_your/kstcez5/) someone asks how you can download backgrounds with it as well and you respond to them by saying they can "Ctrl+F replace poster with art." I tried it with this script to, just for fun. but it doesn't work.

Is there any way to modify this script so that it also picks the first option for a background and not one far down the list?

@JonnyWong16

@JonnyWong16
Copy link
Author

JonnyWong16 commented Apr 3, 2024

Yes, Gracenote also supplies movie artwork.

Replacing poster with art should work (posters plural should be arts plural).

@johnloopi
Copy link

Yes, Gracenote also supplies movie artwork.

Replacing poster with art should work (posters plural should be arts plural).

Thank you so much, I just tested in command prompt and it worked perfectly! I tried it yesterday myself by replacing "poster" with "art" and "posters" with "arts", but it didn't work. When you typed what I should change I looked over the script again and someplaces were "artss" instead of "arts", so I had to fix my typos, but now everything works perfectly. :)

I have also bookmarked this, but not yet implemented in script:

Change line 47 to:

    for item in library.all(libtype='season', includeGuids=False):

But why? There are no Gracenote posters for seasons so the script will just skip them anyways.

As Plex will shift to Gracenote for TV Shows as well sometime in the future, but no specific date has been set yet:
https://forums.plex.tv/t/new-artwork-provider/867786/27

Will that part of the script also change season posters, e.g. Season 1, Season 2, Season 3, etc, of a show or just the main poster?

@pettykrooks
Copy link

is there a walkthrough anywhere for how to get this working for noobs?

@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

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