Skip to content

Instantly share code, notes, and snippets.

@JonnyWong16
Last active June 7, 2026 09:12
Show Gist options
  • Select an option

  • Save JonnyWong16/b0e6b2761f8649d811f51866e682464b to your computer and use it in GitHub Desktop.

Select an option

Save JonnyWong16/b0e6b2761f8649d811f51866e682464b to your computer and use it in GitHub Desktop.
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.
#!/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()
if not posters:
print(f" - WARNING: No available posters for {item.title}.")
return
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:
print(f" - Poster provider is '{selected_poster.provider}' for {item.title}.")
if 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 and locked {provider_poster.provider} poster for {item.title}.")
else:
item.lockPoster()
print(f" - Locked {selected_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()
if not arts:
print(f" - WARNING: No available art for {item.title}.")
return
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:
print(f" - Art provider is '{selected_art.provider}' for {item.title}.")
if 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 and locked {provider_art.provider} art for {item.title}.")
else:
item.lockArt()
print(f" - Locked {selected_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.")
@ThatBOSSChris

ThatBOSSChris commented Jun 24, 2025

Copy link
Copy Markdown

@JonnyWong16 Got the following error running python select_tmdb_poster.py --library "Movies" --include_locked --poster --art without Tautulli:

Bush Mama (1979)
  Checking poster...
  - Poster provider is 'tmdb' for Bush Mama.
  - Locked tmdb poster for Bush Mama.
  Checking art...
  - WARNING: No art selected for Bush Mama.
Traceback (most recent call last):
  File "select_tmdb_poster.py", line 178, in <module>
    select_library(library, opts.include_locked, opts.poster, opts.poster_provider, opts.art, opts.art_provider)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "select_tmdb_poster.py", line 81, in select_library
    select_item(
    ~~~~~~~~~~~^
        item,
        ^^^^^
    ...<4 lines>...
        art_provider=art_provider
        ^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "select_tmdb_poster.py", line 104, in select_item
    select_art(item, include_locked, art_provider)
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "select_tmdb_poster.py", line 156, in select_art
    elif skip_art and selected_art:
         ^^^^^^^^
UnboundLocalError: cannot access local variable 'skip_art' where it is not associated with a value

Any ideas?

@JonnyWong16

Copy link
Copy Markdown
Author

Fixed.

@ThatBOSSChris

Copy link
Copy Markdown

Appreciated

@rebelancap

rebelancap commented Mar 24, 2026

Copy link
Copy Markdown

This is fantastic, thank you. Like @pettykrooks, I have a mac, so I needed to make some adjustments to help it find my homebrew python.

~/plex-apps/select_tmdb_poster
├── select_tmdb_poster.py
└── select_tmdb_poster.sh

1 directory, 2 files

Then my select_tmdb_poster.sh contents:

#!/bin/bash
/opt/homebrew/bin/python3 ~/plex-apps/select_tmdb_poster/select_tmdb_poster.py "$@"

No need to edit select_tmdb_poster.py at all.
Then in Tautulli, you select select_tmdb_poster.sh

@rando-smook

Copy link
Copy Markdown

How can you do this if your Plex Media Server is on an Nvidia Shield Pro, with attached storage?

Thanks

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