-
-
Save JonnyWong16/9640557cf459896a8b7e1da8863b0485 to your computer and use it in GitHub Desktop.
| #!/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.") |
As my server is running at close to 100% CPU, while it reprocesses all the assets, I was wondering...
Is it possible to have an option to compare the size of the existing file with the one to be written and NOT overwrite if the files are the same size?
The reason is that most of the time I do have the file already, because I ran this type of script. However, I then later come across a movie or tv show and decide to change one or more of its assets. Usually, I later run this script with the --overwrite and it then overwrite the many assets, even though most of them are the same. However, Plex sees the date change and put the hurt on my CPU again for several hours.
This way I could just set a task schedule for run the script weekly or so without worrying about overloading the CPU each time.
No, that is outside the scope of the script. You can modify the script if you want more sophisticated file comparisons.
Understood, thanks.
I had the exact same high CPU issue and didn't want to rescan huge movies library. I used AI to write some python code that scans every movie folder and creates text file with the full paths to folders that had no poster.jpg in it. The script then runs a FOR loop on the list and finds largest *.mkv, mp4 etc file in the folder, then uses plexapi to lookup the movie filename in Plex and return the Plex ratingKey that I used to call JonnyWong's script using the ratingKey argument. Instead of running his script on all movies the script is run once per missing poster (in loop) and directly targets that movie, limiting Plex changes, rescanning.
(Part 2)
If all movies in Plex are matched and have nice posters beforehand all is good. Unfortunately I had a lot of movies that had no real poster in plex, so Jonny's script dumped a poster.jpg of the generic screenshot image that plex makes when there is no poster. In a huge library its a major pain to find the movies without posters, but Jonny's script actually made it easier. Since every Plex generic screenshot is a rectangular 720 pixel wide jpg of varying (<720) heights I used a bash script that loops the movies folders and runs mediainfo on every poster.jog and spits out a list of folder paths containing a poster.jpg that is exactly 720 pixels wide and less than 720 pixels high.
I manually went through the list and matched in Plex or added my own posters so all of the missing ones were fixed.
Last step was running a bash script to loop through that same list and delete the 720 wide poster.jpg files.
Now you run your python (from up top) and Jonny's script will create poster.jpg of all the new posters you fixed.
Since the python loop w/ Jonny script only accesses missing poster.jpg I scheduled it nightly so every new movie has its own poster.jpg the next day.
Hope this helps. Thanks to Jonny's script all my posters are perfect and work across Plex, Emby and Jellyfin.
As my server is running at close to 100% CPU, while it reprocesses all the assets, I was wondering...
Is it possible to have an option to compare the size of the existing file with the one to be written and NOT overwrite if the files are the same size?
The reason is that most of the time I do have the file already, because I ran this type of script. However, I then later come across a movie or tv show and decide to change one or more of its assets. Usually, I later run this script with the --overwrite and it then overwrite the many assets, even though most of them are the same. However, Plex sees the date change and put the hurt on my CPU again for several hours.
This way I could just set a task schedule for run the script weekly or so without worrying about overloading the CPU each time.
Love the simple fixes! Thank you!!!