How to Download Movie Posters from Your Plex Server
When you add a movie to Plex and then scan the library for changes, Plex will download the posters for the movie. After it downloads the posters Plex will then select one of the posters to display for that movie.
Some people prefer to add their posters for their movies. They will use the Plex Web app to upload a poster and then use that poster instead of the one Plex selects.
I wanted a way of being able to better manage my movie posters, instead of leaving it to Plex. This post will outline how I can download my current Plex posters to prevent the movie posters from being changed by Plex.
My Plex poster issue
Having Plex download posters or uploading my own makes it easy to get posters for my movies. There is one small issue, however, that doing this doesn't address.
The issue I have found is that posters that are downloaded from Plex or uploaded using the Web app are stored in bundles. These are folders in the Plex data directory that contain all the files for the movie.
This makes it difficult to find and store the poster with the movie files in case Plex changes it in the future, or the posters downloaded by Plex no longer exist. This could mean an entirely new set of posters is available in Plex.
To solve this issue, I decided to create a Python script that would download my existing movie posters to the folder containing the movie files. I can then change my movie library to use local media assets to always have my preferred poster available on Plex.
The Python script to download movie posters
To resolve my issue, I wanted a way to download my current movie posters from my Plex server and store them in their respective movie folder.
This will ensure that regardless of the movie posters Plex downloads from the Internet, my local posters will always be available and should be used by Plex.
Before explaining the Python script, I will provide it below
import os import requests import xml.etree.ElementTree as ET import shutil plex_url = os.environ.get('PLEX_URL') plex_token = os.environ.get('PLEX_TOKEN') library = {library_id} def get_all_media(id): """ Gets all media for a library. Keyword arguments: id -- the id of the library """ response = requests.get('{0}/library/sections/{1}/all?X-Plex-Token={2}'.format(plex_url, library, plex_token)) if response.ok: root = ET.fromstring(response.content) return root else: return None def get_media_path(video_tag): """ Finds the full path to the media item, without the name of the media file. Keyword arguments: video_tag -- the video tag returned by the Plex API """ media_tag = video_tag.find('Media') if media_tag is None: return None part_tag = media_tag.find('Part') if part_tag is None: return None file_path = part_tag.get('file') if file_path: return next_filename(os.path.dirname(file_path)) else: return None def get_poster_url(video_tag): """ Gets the URL of the media's poster. Keyword arguments: video_tag -- the video tag returned by the Plex API """ poster = video_tag.get('thumb') if poster: return '{0}{1}?X-Plex-Token={2}'.format(plex_url, poster, plex_token) else: return None def next_filename(path): """ Finds the next free poster name. The first poster name is simply poster.jpg, while all subsequent posters are in the format: poster-n.jpg, where n is a number. e.g. path_pattern = 'poster-%s.txt': poster-1.txt poster-2.txt poster-3.txt Keyword arguments: path -- the path of the media item """ # Check for the first post file name, and if it doesn't # exist then use that file_path = '{0}/poster.jpg'.format(path) if not os.path.exists(file_path): return file_path # Set the poster naming pattern if at least one poster # exists for the media item path_pattern = '{0}/poster-%s.jpg'.format(path) i = 1 # First do an exponential search while os.path.exists(path_pattern % i): i = i * 2 # Result lies somewhere in the interval (i/2..i] # We call this interval (a..b] and narrow it down until a + 1 = b a, b = (i // 2, i) while a + 1 < b: c = (a + b) // 2 # interval midpoint a, b = (c, b) if os.path.exists(path_pattern % c) else (a, c) return path_pattern % b def download_poster(poster_url, path): """ Downloads the poster from the URL to the specified path. Keyword arguments: poster_url -- the url of the poster to be downloaded path -- the path of the poster """ response = requests.get(poster_url, stream=True) if response.status_code == 200: with open(path, 'wb') as f: response.raw.decode_content = True shutil.copyfileobj(response.raw, f) else: print("Couldn't download poster. Status code: {0}".format(response.status_code)) root = get_all_media(library) for video_tag in root.findall('Video'): path = get_media_path(video_tag) if not path: print('The path to the media was not found.') continue poster_url = get_poster_url(video_tag) if poster_url: download_poster(poster_url, path)
How to use the script
To use the script, you will need to do the following:
- Install Python.
- After Python is installed, run the following
pip
command to install the dependencies:pip install requests
- Copy the above script and save it as a Python file, for example:
download_posters.py
- Edit the script to replace
{library_id}
with the movies library ID from your Plex server. - Create an environment variable called
PLEX_URL
and set it to the URL of your Plex server. For example:http://localhost:32400
. - Create an environment variable called
PLEX_TOKEN
and set it to your Plex token.
What does the script do?
The above script will make multiple calls to the Plex API to perform the following steps:
- It gets all the movie files by calling the Get All Movies API command. This is done in the
get_all_media
function. - Once all the movies have been retrieved, it will loop through all the movies and then call the
get_media_path
function to get the full path of each movie. This path is used to store the downloaded poster. - Next, the
get_poster_url
function is called to get the URL API Command to download the poster. This URL is the Get a Movie's Poster endpoint. - Once the URL for the poster is known, the
download_poster
function will then download and save the poster. Thenext_filename
function will get the next valid poster file name following the Plex poster naming conventions.
A few notes about the script
Before running the script, there are a few things to keep in mind:
- The script works when it is run from the Plex server since it uses the path of each movie to store the poster.
- If you wish to store the posters in another location, then you can just modify the script to change the location. This will allow the script to be run from another machine.
- Running the Python script multiple times will create additional copies of the same poster. This script does not check to see if the poster exists in the folder, it simply adds the poster to the folder, with an incrementing number in the name.
By using the above script, I can download the current poster for each movie on my Plex server. This means I will have a local copy of each poster in the correct movie folder that Plex can use instead of automatically selecting one for me.