-
-
Save JonnyWong16/b0e6b2761f8649d811f51866e682464b to your computer and use it in GitHub Desktop.
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
''' | |
Description: Selects the default TMDB poster and art for items in a Plex library | |
if no poster/art is selected or the current poster/art is from Gracenote. | |
Author: /u/SwiftPanda16 | |
Requires: plexapi | |
Usage: | |
* Change the posters for an entire library: | |
python select_tmdb_poster.py --library "Movies" --poster | |
* Change the art for an entire library: | |
python select_tmdb_poster.py --library "Movies" --art | |
* Change the posters and art for an entire library: | |
python select_tmdb_poster.py --library "Movies" --poster --art | |
* Change the poster for a specific item: | |
python select_tmdb_poster.py --rating_key 1234 --poster | |
* Change the art for a specific item: | |
python select_tmdb_poster.py --rating_key 1234 --art | |
* Change the poster and art for a specific item: | |
python select_tmdb_poster.py --rating_key 1234 --poster --art | |
* By default locked posters are skipped. To update locked posters: | |
python select_tmdb_poster.py --library "Movies" --include_locked --poster --art | |
* To override the preferred provider: | |
python select_tmdb_poster.py --library "Movies" --art --art_provider "fanarttv" | |
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} --poster --art | |
''' | |
import argparse | |
import os | |
import plexapi.base | |
from plexapi.server import PlexServer | |
plexapi.base.USER_DONT_RELOAD_FOR_KEYS.add('fields') | |
# Poster and art providers to replace | |
REPLACE_PROVIDERS = ['gracenote', 'plex', None] | |
# Preferred poster and art provider to use (Note not all providers are availble for all items) | |
# Possible options: tmdb, tvdb, imdb, fanarttv, gracenote, plex | |
PREFERRED_POSTER_PROVIDER = 'tmdb' | |
PREFERRED_ART_PROVIDER = 'tmdb' | |
# ## 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_library( | |
library, | |
include_locked=False, | |
poster=False, | |
poster_provider=PREFERRED_POSTER_PROVIDER, | |
art=False, | |
art_provider=PREFERRED_ART_PROVIDER | |
): | |
for item in library.all(includeGuids=False): | |
# Only reload for fields | |
item.reload(**{k: 0 for k, v in item._INCLUDES.items()}) | |
select_item( | |
item, | |
include_locked=include_locked, | |
poster=poster, | |
poster_provider=poster_provider, | |
art=art, | |
art_provider=art_provider | |
) | |
def select_item( | |
item, | |
include_locked=False, | |
poster=False, | |
poster_provider=PREFERRED_POSTER_PROVIDER, | |
art=False, | |
art_provider=PREFERRED_ART_PROVIDER | |
): | |
print(f"{item.title} ({item.year})") | |
if poster: | |
select_poster(item, include_locked, poster_provider) | |
if art: | |
select_art(item, include_locked, art_provider) | |
def select_poster(item, include_locked=False, provider=PREFERRED_POSTER_PROVIDER): | |
print(" Checking poster...") | |
if item.isLocked('thumb') and not include_locked: # PlexAPI 4.5.10 | |
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_poster = ' Skipping.' if selected_poster.provider not in REPLACE_PROVIDERS else '' | |
print(f" - Poster provider is '{selected_poster.provider}' for {item.title}.{skipping_poster}") | |
if posters and (selected_poster is None or selected_poster.provider in REPLACE_PROVIDERS): | |
# Fallback to first poster if no preferred provider posters are available | |
provider_poster = next((p for p in posters if p.provider == provider), posters[0]) | |
# Selecting the poster automatically locks it | |
provider_poster.select() | |
print(f" - Selected {provider_poster.provider} poster for {item.title}.") | |
def select_art(item, include_locked=False, provider=PREFERRED_ART_PROVIDER): | |
print(" Checking art...") | |
if item.isLocked('art') and not include_locked: # PlexAPI 4.5.10 | |
print(f" - Locked art for {item.title}. Skipping.") | |
return | |
arts = item.arts() | |
selected_art = next((p for p in arts if p.selected), None) | |
if selected_art is None: | |
print(f" - WARNING: No art selected for {item.title}.") | |
else: | |
skipping_art = ' Skipping.' if selected_art.provider not in REPLACE_PROVIDERS else '' | |
print(f" - Art provider is '{selected_art.provider}' for {item.title}.{skipping_art}") | |
if arts and (selected_art is None or selected_art.provider in REPLACE_PROVIDERS): | |
# Fallback to first art if no preferred provider arts are available | |
provider_art = next((p for p in arts if p.provider == provider), arts[0]) | |
# Selecting the art automatically locks it | |
provider_art.select() | |
print(f" - Selected {provider_art.provider} art 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') | |
parser.add_argument('--poster', action='store_true') | |
parser.add_argument('--poster_provider', default=PREFERRED_POSTER_PROVIDER) | |
parser.add_argument('--art', action='store_true') | |
parser.add_argument('--art_provider', default=PREFERRED_ART_PROVIDER) | |
opts = parser.parse_args() | |
plex = PlexServer(PLEX_URL, PLEX_TOKEN) | |
if opts.rating_key: | |
item = plex.fetchItem(opts.rating_key) | |
select_item(item, opts.include_locked, opts.poster, opts.poster_provider, opts.art, opts.art_provider) | |
elif opts.library: | |
library = plex.library.section(opts.library) | |
select_library(library, opts.include_locked, opts.poster, opts.poster_provider, opts.art, opts.art_provider) | |
else: | |
print("No --rating_key or --library specified. Exiting.") |
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.
is it possible to do it for tv shows and season posters?
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.
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.
Insert at line 67:
if skipping:
item.lockPoster()
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?
Yes, Gracenote also supplies movie artwork.
Replacing poster
with art
should work (posters
plural should be arts
plural).
Yes, Gracenote also supplies movie artwork.
Replacing
poster
withart
should work (posters
plural should bearts
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?
is there a walkthrough anywhere for how to get this working for noobs?
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
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
- Change the poster for a specific item:
thanks. and now I know some small amount of python
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
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.
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!
Hi, this tool has been fantastic for me! thanks a lot!
Is there anything like this for shows?
thank you!
I just wanna say how much I appreciate this. Thank you. The homemade posters that plex is adding by default now are truly awful.
Thank you for providing this script. It's very useful.
Thank you for very useful script, using it for a while now.
I notices that posters for movies in tmdb website change, not constantly but now and then.
Now script is skipping poster if its already tmdb.
Is it possible to change the script for it to update already selected tmdb poster for the new one that is default at tmdb website?
Hi, this tool has been fantastic for me! thanks a lot!
Is there anything like this for shows?
thank you!
@shorshiii There is nothing in this script unique to movies, it also works for tv shows.
Thank you for very useful script, using it for a while now. I notices that posters for movies in tmdb website change, not constantly but now and then. Now script is skipping poster if its already tmdb. Is it possible to change the script for it to update already selected tmdb poster for the new one that is default at tmdb website?
@SSauliusB You can remove the check for Gracenote in the script.
I keep getting the following logs:
Zombieland (2009) Checking art... - Art provider is 'None' for Zombieland. Skipping.
I added 'none' to REPLACE_PROVIDERS but that doesn't seem to work.
How can I use this script to replace 'none' with 'tmdb'?
I keep getting the following logs:
Zombieland (2009) Checking art... - Art provider is 'None' for Zombieland. Skipping.
I added 'none' to REPLACE_PROVIDERS but that doesn't seem to work. How can I use this script to replace 'none' with 'tmdb'?
Hello, had same problem and my solution was adding selected_poster.provider == None
I keep getting the following logs:
Zombieland (2009) Checking art... - Art provider is 'None' for Zombieland. Skipping.
I added 'none' to REPLACE_PROVIDERS but that doesn't seem to work. How can I use this script to replace 'none' with 'tmdb'?Hello, had same problem and my solution was adding selected_poster.provider == None
Thanks! Got it working :)
Does the script support other overlays then English ones?
For my non English speaking family member I created a library in a different language, but the posters are downloaded in English, whilst the title and description of the movie is in the "correct" language.
tmdb has the image available, for example: https://www.themoviedb.org/movie/533535-deadpool-wolverine?language=uk-UA
@KevinBarselaar @DeadSyncLTU I added None
to the list.
@deniax2 Plex doesn't provide language data for the posters so there is no way to select a specific language.
Am I right in thinking that this updates my Plex library with the set poster on https://www.themoviedb.org/ ?
If yes, how the hell do I get it working.
TIA
I am getting this in my log. How do i fix it? thanks
Tautulli Notifiers :: Script returned:
No --rating_key or --library specified. Exiting.
Your PlexAPI version is out-of-date.
Tautulli issue already answered above.