Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 54 additions & 10 deletions utility/merge_multiepisodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io
import math
import os
import re
import requests
from collections import defaultdict
from plexapi.server import PlexServer
Expand Down Expand Up @@ -68,6 +69,8 @@ def group_episodes(plex, library, show, renumber, composite_thumb):

for season in show.seasons():
groups = defaultdict(list)
titles_toMerge = []
titlesSort_toMerge = []
startIndex = None

for episode in season.episodes():
Expand All @@ -76,19 +79,22 @@ def group_episodes(plex, library, show, renumber, composite_thumb):
startIndex = episode.index

for index, (first, *episodes) in enumerate(groups.values(), start=startIndex):
title = first.title + ' / '
titleSort = first.titleSort + ' / '
summary = first.summary + '\n\n'
titles_toMerge = [first.title]
titlesSort_toMerge = [first.titleSort]
summary = [first.summary]
writers = []
directors = []

for episode in episodes:
title += episode.title + ' / '
titleSort += episode.titleSort + ' / '
summary += episode.summary + '\n\n'
titles_toMerge.append(episode.title)
titlesSort_toMerge.append(episode.titleSort)
summary.append(episode.summary)
writers.extend([writer.tag for writer in episode.writers])
directors.extend([director.tag for director in episode.directors])

writers = list(set(writers))
directors = list(set(directors))

if episodes:
if composite_thumb:
firstImgFile = download_image(
Expand All @@ -103,9 +109,9 @@ def group_episodes(plex, library, show, renumber, composite_thumb):
merge(first, episodes)

first.batchEdits() \
.editTitle(title[:-3]) \
.editSortTitle(titleSort[:-3]) \
.editSummary(summary[:-2]) \
.editTitle(merge_titles(titles_toMerge)) \
.editSortTitle(merge_titles(titlesSort_toMerge)) \
.editSummary('\n\n'.join(summary) \
.editContentRating(first.contentRating) \
.editOriginallyAvailable(first.originallyAvailableAt) \
.addWriter(writers) \
Expand All @@ -118,7 +124,41 @@ def group_episodes(plex, library, show, renumber, composite_thumb):
first.saveEdits()


# Regex pattern to match episode part indicators in titles.
# Matches:
# - partX, ptX, cdX, discX, diskX, dvdX (where X is a number, with optional space)
# - (X) (where X is a number in parentheses)
MERGE_TITLE_PATTERN = r'(\b(part|pt|cd|disc|disk|dvd)\s?\d+|\(\d+\))'

def merge_titles(titles):
merged_titles = []
base_title = None

for title in titles:
# Check if the title contains any of the specified patterns (case insensitive)
match = re.search(MERGE_TITLE_PATTERN, title, re.IGNORECASE)

if match:
# If base title is not yet set, extract it from the first part (ignore case)
if base_title is None:
base_title = re.sub(MERGE_TITLE_PATTERN, '', title, flags=re.IGNORECASE).strip()
else:
# If the current title doesn't match part patterns and we have a base title, merge it
if base_title:
merged_titles.append(base_title) # Append base title once
base_title = None # Reset base title for next potential title
merged_titles.append(title)

# If the last title was part of a merged series, add the base title at the end
if base_title:
merged_titles.append(base_title)

return " | ".join(merged_titles)


def merge(first, episodes):
if not episodes:
return
key = '%s/merge?ids=%s' % (first.key, ','.join([str(r.ratingKey) for r in episodes]))
first._server.query(key, method=first._server._session.put)

Expand Down Expand Up @@ -205,12 +245,16 @@ def create_masks():
parser.add_argument('--library', required=True)
parser.add_argument('--show', required=True)
parser.add_argument('--renumber', action='store_true')
parser.add_argument('--composite_thumb', action='store_true')
parser.add_argument('--composite-thumb', action='store_true')
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)

if not PLEX_URL or not PLEX_TOKEN:
print('Please set PLEX_URL and PLEX_TOKEN environment variables or edit the script to include your Plex server URL and token.')
exit(1)

plex = PlexServer(PLEX_URL, PLEX_TOKEN)
group_episodes(plex, **vars(opts))