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. 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.

How to Download Movie Posters from Your Plex Server

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:

  1. Install Python.
  2. After Python is installed, run the following pip command to install the dependencies:
    pip install requests
  3. Copy the above script and save it as a Python file, for example: download_posters.py
  4. Edit the script to replace {library_id} with the movies library ID from your Plex server.
  5. Create an environment variable called PLEX_URL and set it to the URL of your Plex server. For example: http://localhost:32400.
  6. 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:

  1. It gets all the movie files by calling the Get All Movies API command. This is done in the get_all_media function.
  2. 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.
  3. 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.
  4. Once the URL for the poster is known, the download_poster function will then download and save the poster. The next_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 few things to keep in mind:

  1. The script works when it is run from the Plex server since it uses the path of each movie to store the poster.
  2. 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.
  3. 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.

Photo of Paul Salmon
Started managing a Plex server in November 2014 and has been sharing his experience and what he has learned on Plexopedia. He is exploring and documenting the Plex API to help automate tasks for Plex to reduce the management effort of his server.

Dialogue & Discussion

Subscribe
Display