Merge pull request #375 from JonnyWong16/feature/merge_multiepisodes

Use `batchEdits()` and add `composite_thumb` option to merge_multiepisodes
This commit is contained in:
blacktwin 2023-06-08 23:47:23 -04:00 committed by GitHub
commit 73bf20d86d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -4,7 +4,7 @@
''' '''
Description: Automatically merge multi-episode files in Plex into a single entry. Description: Automatically merge multi-episode files in Plex into a single entry.
Author: /u/SwiftPanda16 Author: /u/SwiftPanda16
Requires: plexapi Requires: plexapi, pillow (optional)
Notes: Notes:
* All episodes **MUST** be organized correctly according to Plex's "Multiple Episodes in a Single File". * All episodes **MUST** be organized correctly according to Plex's "Multiple Episodes in a Single File".
https://support.plex.tv/articles/naming-and-organizing-your-tv-show-files/#toc-4 https://support.plex.tv/articles/naming-and-organizing-your-tv-show-files/#toc-4
@ -26,25 +26,44 @@ Usage:
* With renumbering episodes: * With renumbering episodes:
python merge_multiepisodes.py --library "TV Shows" --show "SpongeBob SquarePants" --renumber python merge_multiepisodes.py --library "TV Shows" --show "SpongeBob SquarePants" --renumber
* With renumbering episodes and composite thumb:
python merge_multiepisodes.py --library "TV Shows" --show "SpongeBob SquarePants" --renumber --composite-thumb
''' '''
import argparse import argparse
import functools
import io
import math
import os import os
import requests
from collections import defaultdict from collections import defaultdict
from plexapi.server import PlexServer from plexapi.server import PlexServer
try:
from PIL import Image, ImageDraw
hasPIL = True
except ImportError:
hasPIL = False
# ## EDIT SETTINGS ## # ## EDIT SETTINGS ##
PLEX_URL = '' PLEX_URL = ''
PLEX_TOKEN = '' PLEX_TOKEN = ''
# Composite Thumb Settings
WIDTH, HEIGHT = 640, 360 # 16:9 aspect ratio
LINE_ANGLE = 25 # degrees
LINE_THICKNESS = 10
# Environmental Variables # Environmental Variables
PLEX_URL = os.getenv('PLEX_URL', PLEX_URL) PLEX_URL = os.getenv('PLEX_URL', PLEX_URL)
PLEX_TOKEN = os.getenv('PLEX_TOKEN', PLEX_TOKEN) PLEX_TOKEN = os.getenv('PLEX_TOKEN', PLEX_TOKEN)
def group_episodes(plex, library, show, renumber): def group_episodes(plex, library, show, renumber, composite_thumb):
show = plex.library.section(library).get(show) show = plex.library.section(library).get(show)
for season in show.seasons(): for season in show.seasons():
@ -71,27 +90,32 @@ def group_episodes(plex, library, show, renumber):
directors.extend([director.tag for director in episode.directors]) directors.extend([director.tag for director in episode.directors])
if episodes: if episodes:
if composite_thumb:
firstImgFile = download_image(
plex.transcodeImage(first.thumbUrl, width=WIDTH, height=HEIGHT)
)
lastImgFile = download_image(
plex.transcodeImage(episodes[-1].thumbUrl, width=WIDTH, height=HEIGHT)
)
compImgFile = create_composite_thumb(firstImgFile, lastImgFile)
first.uploadPoster(filepath=compImgFile)
merge(first, episodes) merge(first, episodes)
first.addWriter(writers, locked=True) first.batchEdits() \
first.addDirector(directors, locked=True) .editTitle(title[:-3]) \
.editSortTitle(titleSort[:-3]) \
edits = { .editSummary(summary[:-2]) \
'title.value': title[:-3], .editContentRating(first.contentRating) \
'title.locked': 1, .editOriginallyAvailable(first.originallyAvailableAt) \
'titleSort.value': titleSort[:-3], .addWriter(writers) \
'titleSort.locked': 1, .addDirector(directors) \
'summary.value': summary[:-2],
'summary.locked': 1,
'originallyAvailableAt.locked': 1,
'contentRating.locked': 1
}
if renumber: if renumber:
edits['index.value'] = index first._edits['index.value'] = index
edits['index.locked'] = 1 first._edits['index.locked'] = 1
first.edit(**edits) first.saveEdits()
def merge(first, episodes): def merge(first, episodes):
@ -99,12 +123,94 @@ def merge(first, episodes):
first._server.query(key, method=first._server._session.put) first._server.query(key, method=first._server._session.put)
def download_image(url):
r = requests.get(url, stream=True)
r.raw.decode_content = True
return r.raw
def create_composite_thumb(firstImgFile, lastImgFile):
mask, line = create_masks()
# Open and crop first image
firstImg = Image.open(firstImgFile)
width, height = firstImg.size
firstImg = firstImg.crop(
(
(width - WIDTH) // 2,
(height - HEIGHT) // 2,
(width + WIDTH) // 2,
(height + HEIGHT) // 2
)
)
# Open and crop last image
lastImg = Image.open(lastImgFile)
width, height = lastImg.size
lastImg = lastImg.crop(
(
(width - WIDTH) // 2,
(height - HEIGHT) // 2,
(width + WIDTH) // 2,
(height + HEIGHT) // 2
)
)
# Create composite image
comp = Image.composite(line, Image.composite(firstImg, lastImg, mask), line)
# Return composite image as file-like object
compImgFile = io.BytesIO()
comp.save(compImgFile, format='jpeg')
compImgFile.seek(0)
return compImgFile
@functools.lru_cache(maxsize=None)
def create_masks():
scale = 3 # For line anti-aliasing
offset = HEIGHT // 2 * math.tan(LINE_ANGLE * math.pi / 180)
# Create diagonal mask
mask = Image.new('L', (WIDTH, HEIGHT), 0)
draw = ImageDraw.Draw(mask)
draw.polygon(
(
(0, 0),
(WIDTH // 2 + offset, 0),
(WIDTH // 2 - offset, HEIGHT),
(0, HEIGHT)
),
fill=255
)
# Create diagonal line (use larger image then scale down with anti-aliasing)
line = Image.new('L', (scale * WIDTH, scale * HEIGHT), 0)
draw = ImageDraw.Draw(line)
draw.line(
(
(scale * (WIDTH // 2 + offset), -scale),
(scale * (WIDTH // 2 - offset), scale * (HEIGHT + 1))
),
fill=255,
width=scale * LINE_THICKNESS
)
line = line.resize((WIDTH, HEIGHT), Image.Resampling.LANCZOS)
return mask, line
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--library', required=True) parser.add_argument('--library', required=True)
parser.add_argument('--show', required=True) parser.add_argument('--show', required=True)
parser.add_argument('--renumber', action='store_true') parser.add_argument('--renumber', action='store_true')
parser.add_argument('--composite_thumb', action='store_true')
opts = parser.parse_args() opts = parser.parse_args()
if opts.composite_thumb and not hasPIL:
print('PIL is not installed. Please install `pillow` to create composite thumbnails.')
exit(1)
plex = PlexServer(PLEX_URL, PLEX_TOKEN) plex = PlexServer(PLEX_URL, PLEX_TOKEN)
group_episodes(plex, **vars(opts)) group_episodes(plex, **vars(opts))