Skip to content

Instantly share code, notes, and snippets.

@JonnyWong16
Last active May 3, 2026 16:21
Show Gist options
  • Select an option

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

Select an option

Save JonnyWong16/9640557cf459896a8b7e1da8863b0485 to your computer and use it in GitHub Desktop.
Saves posters, art, logos, square art, and themes from Plex to same folder as the media files.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Description: Saves posters, art, logos, square art, and themes from Plex to same folder as the media files.
Author: /u/SwiftPanda16
Requires: plexapi, tqdm (optional)
Usage:
* Save posters for an entire library:
python save_resources.py --library "TV Shows" --poster
* Save art for an entire library:
python save_resources.py --library "Music" --art
* Save posters, art, logos, and square art for an entire library:
python save_resources.py --library "Movies" --poster --art --logo --squareArt
* Save posters and art for a specific media type in a library:
python save_resources.py --library "TV Shows" --libtype season --poster --art
* Save themes for an entire library:
python save_resources.py --library "TV Shows" --theme
* Save posters for a specific item:
python save_resources.py --rating_key 1234 --poster
* Save art for a specific item:
python save_resources.py --rating_key 1234 --art
* Save posters, art, logos, and square art for a specific item:
python save_resources.py --rating_key 1234 --poster --art --logo --squareArt
* Overwrite existing images (add --overwrite flag):
python save_resources.py --library "Movies" --poster --overwrite
'''
import argparse
from pathlib import Path
from plexapi.server import PlexServer
from plexapi.utils import download
PLEX_URL = 'http://localhost:32400'
PLEX_TOKEN = 'XXXXXXXXXXXXXXXXXXXX'
# Specify the mapped docker folder paths {host: container}. Leave blank {} if non-docker.
MAPPED_FOLDERS = {
'/mnt/movies': '/movies',
'/mnt/tvshows': '/tv',
}
_MAPPED_FOLDERS = {Path(host): Path(container) for host, container in MAPPED_FOLDERS.items()}
def map_path(file_path):
for host, container in _MAPPED_FOLDERS.items():
if container in file_path.parents:
return host / file_path.relative_to(container)
return file_path
def save_library(
library,
libtype=None,
poster=False,
art=False,
logo=False,
squareArt=False,
theme=False,
overwrite=False
):
for item in library.all(libtype=libtype, includeGuids=False):
save_item(
item,
poster=poster,
art=art,
logo=logo,
squareArt=squareArt,
theme=theme,
overwrite=overwrite
)
def save_item(
item,
poster=False,
art=False,
logo=False,
squareArt=False,
theme=False,
overwrite=False
):
if hasattr(item, 'locations'):
file_path = Path(item.locations[0])
else:
file_path = Path(next(iter(item)).locations[0])
save_path = map_path(file_path)
if save_path.is_file():
save_path = save_path.parent
print(f"{item.title}{f' ({item.year})' if hasattr(item, 'year') else ''}")
if poster:
if item.type == 'season':
filename = f'season{item.seasonNumber:02d}.jpg'
else:
filename = 'poster.jpg'
download_resource('poster', item.posterUrl, save_path, filename, overwrite=overwrite)
if art:
download_resource('art', item.artUrl, save_path, 'art.jpg', overwrite=overwrite)
if logo:
download_resource('logo', item.logoUrl, save_path, 'logo.png', overwrite=overwrite)
if squareArt:
download_resource('squareArt', item.squareArtUrl, save_path, 'squareArt.jpg', overwrite=overwrite)
if theme:
download_resource('theme', item.themeUrl, save_path, 'theme.mp3', overwrite=overwrite)
def download_resource(resource, resource_url, save_path, filename, overwrite=False):
full_path = save_path / filename
if not overwrite and full_path.exists():
print(f" └─ {resource.capitalize()} already exists at {full_path}. Skipping.")
return
if not resource_url:
print(f" └─ No {resource} set. Skipping.")
return
print(f" └─ Downloading {full_path}")
try:
download(
url=resource_url,
token=plex._token,
filename=filename,
savepath=save_path,
showstatus=True # Requires `tqdm` package
)
except Exception as e:
print(f" └─ Failed to download {resource}: {e}")
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--rating_key', type=int)
parser.add_argument('--library')
parser.add_argument('--libtype', choices=['movie', 'show', 'season', 'artist', 'album'])
parser.add_argument('--poster', action='store_true')
parser.add_argument('--art', action='store_true')
parser.add_argument('--logo', action='store_true')
parser.add_argument('--squareArt', action='store_true')
parser.add_argument('--theme', action='store_true')
parser.add_argument('--overwrite', action='store_true')
opts = parser.parse_args()
plex = PlexServer(PLEX_URL, PLEX_TOKEN)
if opts.rating_key:
item = plex.fetchItem(opts.rating_key)
save_item(
item,
poster=opts.poster,
art=opts.art,
logo=opts.logo,
squareArt=opts.squareArt,
theme=opts.theme,
overwrite=opts.overwrite
)
elif opts.library:
library = plex.library.section(opts.library)
save_library(
library,
libtype=opts.libtype,
poster=opts.poster,
art=opts.art,
logo=opts.logo,
squareArt=opts.squareArt,
theme=opts.theme,
overwrite=opts.overwrite
)
else:
print("No --rating_key or --library specified. Exiting.")
@JonnyWong16
Copy link
Copy Markdown
Author

Updated script with --logo and --squareArt options, as well as a flag to overwrite existing files (the script skips existing files by default now). Background art has been changed to art.jpg instead of background.jpg.

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