Skip to content
This repository has been archived by the owner on Oct 13, 2024. It is now read-only.

feat(shows): add support for tv shows #337

Merged
merged 25 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fd3e142
feat(shows): add support for tv shows
ReenigneArcher Jan 22, 2024
3a11ae1
tests: update unit tests for tv shows
ReenigneArcher Jan 23, 2024
8624dca
fixes from code review
ReenigneArcher Jan 26, 2024
707cc59
allow overwriting locked themes during ci testing
ReenigneArcher Jan 26, 2024
846d475
better support for new plex series agent
ReenigneArcher Jan 26, 2024
019ef42
improve getting theme provider
ReenigneArcher Jan 26, 2024
b4a8681
fix media upload path
ReenigneArcher Jan 26, 2024
3503bfd
convert external id to str when an int is provided
ReenigneArcher Jan 27, 2024
3bde25b
fix theme upload conditions
ReenigneArcher Jan 27, 2024
18385f0
update plexhints action
ReenigneArcher Jan 27, 2024
acfedac
improve tests
ReenigneArcher Jan 27, 2024
7c75ad5
set correct type for item arguments
ReenigneArcher Jan 27, 2024
c8ebf9b
update theme providers
ReenigneArcher Jan 27, 2024
9059a2e
fix plex provided detection logic
ReenigneArcher Jan 27, 2024
6defaf3
properly handle movies and series in plex listener
ReenigneArcher Jan 27, 2024
71c154b
more fixes
ReenigneArcher Jan 27, 2024
005dbbb
various fixes
ReenigneArcher Jan 28, 2024
2bb860e
Merge branch 'master' into feat(shows)-add-support-for-tv-shows
ReenigneArcher Jan 28, 2024
64a6e32
improve theme provider detection
ReenigneArcher Jan 28, 2024
f151635
Merge branch 'master' into feat(shows)-add-support-for-tv-shows
ReenigneArcher Jan 28, 2024
d31f208
enable overwriting plex themes during CI
ReenigneArcher Jan 28, 2024
b4bbabc
Merge branch 'master' into feat(shows)-add-support-for-tv-shows
ReenigneArcher Jan 29, 2024
f1d6825
fix docstring type
ReenigneArcher Jan 29, 2024
8a2bd26
Update README.rst
ReenigneArcher Jan 30, 2024
3ce3b2d
Merge branch 'master' into feat(shows)-add-support-for-tv-shows
ReenigneArcher Feb 29, 2024
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
6 changes: 3 additions & 3 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,15 @@ jobs:
env:
PLEXAPI_PLEXAPI_TIMEOUT: "60"
id: bootstrap
uses: LizardByte/plexhints@v2023.1226.203406
uses: LizardByte/plexhints@feat(action)-add-legacy-tv-agents # todo - switch back to release version
ReenigneArcher marked this conversation as resolved.
Show resolved Hide resolved
with:
additional_server_queries_put: >-
/system/agents/com.plexapp.agents.imdb/config/1?order=com.plexapp.agents.imdb%2Cdev.lizardbyte.themerr-plex
/system/agents/com.plexapp.agents.themoviedb/config/1?order=com.plexapp.agents.themoviedb%2Cdev.lizardbyte.themerr-plex
/system/agents/com.plexapp.agents.themoviedb/config/2?order=com.plexapp.agents.themoviedb%2Cdev.lizardbyte.themerr-plex
/system/agents/com.plexapp.agents.themoviedb/config/2?order=com.plexapp.agents.thetvdb%2Cdev.lizardbyte.themerr-plex
plugin_bundles_to_install: >-
Themerr-plex.bundle
without_shows: true
without_music: true
without_photos: true

Expand All @@ -211,7 +212,6 @@ jobs:
run: |
python -m pytest \
-rxXs \
--maxfail=1 \
--tb=native \
--verbose \
--cov=Contents/Code \
Expand Down
46 changes: 33 additions & 13 deletions Contents/Code/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
from plexhints.decorator_kit import handler # decorator kit
from plexhints.locale_kit import Locale
from plexhints.log_kit import Log # log kit
from plexhints.model_kit import Movie # model kit
from plexhints.model_kit import MetadataModel # model kit
from plexhints.object_kit import MessageContainer, MetadataSearchResult, SearchResult # object kit
from plexhints.prefs_kit import Prefs # prefs kit

# imports from Libraries\Shared
from typing import Optional
from typing import Optional, Union

try:
# get the original Python builtins module
Expand Down Expand Up @@ -216,9 +216,9 @@ def main():
pass


class Themerr(Agent.Movies):
class Themerr(object):
"""
Class representing the Themerr-plex Movie Agent.
Class representing the Themerr-plex Agent.

This class defines the metadata agent. See the archived Plex documentation
`Defining an agent class
Expand Down Expand Up @@ -270,9 +270,12 @@ class Themerr(Agent.Movies):
accepts_from = []
contributes_to = contributes_to

@staticmethod
def search(results, media, lang, manual):
# type: (SearchResult, Media.Movie, str, bool) -> Optional[SearchResult]
def __init__(self, *args, **kwargs):
super(Themerr, self).__init__(*args, **kwargs)
self.agent_type = "movies" if isinstance(self, Agent.Movies) else "tv_shows"

def search(self, results, media, lang, manual):
# type: (SearchResult, Union[Media.Movie, Media.TV_Show], str, bool) -> Optional[SearchResult]
"""
Search for an item.

Expand All @@ -286,7 +289,7 @@ def search(results, media, lang, manual):
----------
results : SearchResult
An empty container that the developer should populate with potential matches.
media : Media.Movie
media : Union[Media.Movie, Media.TV_Show]
An object containing hints to be used when performing the search.
lang : str
A string identifying the user’s currently selected language. This will be one of the constants added to the
Expand Down Expand Up @@ -319,14 +322,18 @@ def search(results, media, lang, manual):
if media.primary_agent == 'dev.lizardbyte.retroarcher-plex':
media_id = 'games-%s' % re.search(r'((igdb)-(\d+))', media.primary_metadata.id).group(1)
else:
media_id = 'movies-%s-%s' % (media.primary_agent.rsplit('.', 1)[-1], media.primary_metadata.id)
media_id = '{}-{}-{}'.format(
self.agent_type,
media.primary_agent.rsplit('.', 1)[-1],
media.primary_metadata.id
)
# e.g. = 'movies-imdb-tt0113189'
# e.g. = 'movies-themoviedb-710'

results.Append(MetadataSearchResult(
id=media_id,
name=media.primary_metadata.title,
year=media.primary_metadata.year,
year=getattr(media.primary_metadata, 'year', None), # TV Shows don't have a year attribute
score=100,
lang=lang, # no lang to get from db
thumb=None # no point in adding thumb since plex won't show it anywhere
Expand All @@ -339,7 +346,7 @@ def search(results, media, lang, manual):

@staticmethod
def update(metadata, media, lang, force):
# type: (Movie, Media.Movie, str, bool) -> Optional[Movie]
# type: (MetadataModel, Union[Media.Movie, Media.TV_Show], str, bool) -> MetadataModel
"""
Update metadata for an item.

Expand All @@ -351,10 +358,10 @@ def update(metadata, media, lang, force):

Parameters
----------
metadata : object
metadata : MetadataModel
A pre-initialized metadata object if this is the first time the item is being updated, or the existing
metadata object if the item is being refreshed.
media : object
media : Union[Media.Movie, Media.TV_Show]
An object containing information about the media hierarchy in the database.
lang : str
A string identifying which language should be used for the metadata. This will be one of the constants
Expand All @@ -363,6 +370,11 @@ def update(metadata, media, lang, force):
A boolean value identifying whether the user forced a full refresh of the metadata. If this argument is
``True``, all metadata should be refreshed, regardless of whether it has been populated previously.

Returns
-------
MetadataModel
The metadata object.

Examples
--------
>>> Themerr().update(metadata=..., media=..., lang='en', force=True)
Expand All @@ -375,3 +387,11 @@ def update(metadata, media, lang, force):
update_plex_item(rating_key=rating_key)

return metadata


class ThemerrMovies(Themerr, Agent.Movies):
agent_type_verbose = "Movies"


class ThemerrTvShows(Themerr, Agent.TV_Shows):
agent_type_verbose = "TV"
15 changes: 10 additions & 5 deletions Contents/Code/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@
themerr_data_directory = os.path.join(plugin_support_data_directory, plugin_identifier, 'DataItems')

contributes_to = [
'tv.plex.agents.movie',
'com.plexapp.agents.imdb',
'com.plexapp.agents.themoviedb',
# 'com.plexapp.agents.thetvdb', # not available as movie agent
'dev.lizardbyte.retroarcher-plex'
'tv.plex.agents.movie', # new movie agent
'tv.plex.agents.series', # new tv show agent
'com.plexapp.agents.imdb', # legacy movie agent
'com.plexapp.agents.themoviedb', # legacy movie and tv show agent
'com.plexapp.agents.thetvdb', # legacy tv show agent
'dev.lizardbyte.retroarcher-plex' # retroarcher plugin
]

guid_map = dict(
Expand Down Expand Up @@ -73,13 +74,15 @@
game_franchises='[GAME FRANCHISE]: ',
movies='[MOVIE]: ',
movie_collections='[MOVIE COLLECTION]: ',
tv_shows='[TV SHOW]: ',
)
url_prefix = dict(
games='https://www.igdb.com/games/',
game_collections='https://www.igdb.com/collections/',
game_franchises='https://www.igdb.com/franchises/',
movies='https://www.themoviedb.org/movie/',
movie_collections='https://www.themoviedb.org/collection/',
tv_shows='https://www.themoviedb.org/tv/',
)

# two additional strings to fill in later, item title and item url
Expand All @@ -97,6 +100,8 @@
movie_collections='{}&labels={}&template={}&title={}{}&{}={}{}'.format(
base_url, issue_label, issue_template, title_prefix['movie_collections'], '{}', url_name,
url_prefix['movie_collections'], '{}'),
tv_shows='{}&labels={}&template={}&title={}{}&{}={}{}'.format(
base_url, issue_label, issue_template, title_prefix['tv_shows'], '{}', url_name, url_prefix['tv_shows'], '{}'),
)

media_type_dict = dict(
Expand Down
1 change: 1 addition & 0 deletions Contents/Code/default_prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
bool_remove_unused_posters='False',
bool_auto_update_items='True',
bool_auto_update_movie_themes='True',
bool_auto_update_tv_themes='True',
bool_auto_update_collection_themes='True',
bool_update_collection_metadata_plex_movie='False',
bool_update_collection_metadata_legacy='True',
Expand Down
37 changes: 31 additions & 6 deletions Contents/Code/plex_api_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,26 @@
database = 'imdb'
database_id = item.guid.split('://')[1].split('?')[0]

elif item.type == 'show':
ReenigneArcher marked this conversation as resolved.
Show resolved Hide resolved
split_guid = item.guid.split('://')
agent = split_guid[0]
if agent == 'com.plexapp.agents.themoviedb':
database_type = 'tv_shows'
database = 'themoviedb'
database_id = item.guid.split('://')[1].split('?')[0]
elif agent == 'com.plexapp.agents.thetvdb':
database_type = 'tv_shows'
temp_database = 'thetvdb'
temp_database_id = item.guid.split('://')[1].split('?')[0]

# ThemerrDB does not have TVDB IDs, so we need to convert it to TMDB ID
database_id = tmdb_helper.get_tmdb_id_from_external_id(
external_id=temp_database_id,
database='tvdb',
item_type='tv',
)
database = 'themoviedb' if database_id else None

elif item.type == 'collection':
# this is tricky since collections don't match up with any of the databases
# we'll use the collection title and try to find a match
Expand Down Expand Up @@ -715,15 +735,20 @@
Log.Debug('Themerr-plex is disabled for agent "{}"'.format(section.agent))
continue

all_items = []

# get all the items in the section
media_items = section.all() if Prefs['bool_auto_update_movie_themes'] else []
if section.type == 'movie':
media_items = section.all() if Prefs['bool_auto_update_movie_themes'] else []

# get all collections in the section
collections = section.collections() if Prefs['bool_auto_update_collection_themes'] else []
# get all collections in the section
collections = section.collections() if Prefs['bool_auto_update_collection_themes'] else []

# combine the items and collections into one list
# this is done so that we can process both items and collections in the same loop
all_items = media_items + collections
# combine the items and collections into one list
# this is done so that we can process both items and collections in the same loop
all_items = media_items + collections
elif section.type == 'show':
all_items = section.all() if Prefs['bool_auto_update_tv_themes'] else []

Check warning on line 751 in Contents/Code/plex_api_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/plex_api_helper.py#L750-L751

Added lines #L750 - L751 were not covered by tests

for item in all_items:
if item.ratingKey not in q.queue:
Expand Down
1 change: 1 addition & 0 deletions Contents/Code/themerr_db_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
game_franchises={'igdb': 'id'},
movies={'themoviedb': 'id', 'imdb': 'imdb_id'},
movie_collections={'themoviedb': 'id'},
tv_shows={'themoviedb': 'id'},
)

lock = Lock()
Expand Down
39 changes: 28 additions & 11 deletions Contents/Code/tmdb_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@
tmdb_base_url = 'http://127.0.0.1:32400/services/tmdb?uri='


def get_tmdb_id_from_imdb_id(imdb_id):
# type: (str) -> Optional[int]
def get_tmdb_id_from_external_id(external_id, database, item_type):
# type: (str, str, str) -> Optional[int]
"""
Convert IMDB ID to TMDB ID.

Use the builtin Plex tmdb api service to search for a movie by IMDB ID.

Parameters
----------
imdb_id : str
IMDB ID to convert.
external_id : str
External ID to convert.
database : str
Database to search. Must be one of 'imdb' or 'tvdb'.
item_type : str
Item type to search. Must be one of 'movie' or 'tv'.

Returns
-------
Expand All @@ -37,25 +41,38 @@

Examples
--------
>>> get_tmdb_id_from_imdb_id(imdb_id='tt1254207')
>>> get_tmdb_id_from_external_id(imdb_id='tt1254207', database='imdb', item_type='movie')
10378
>>> get_tmdb_id_from_external_id(imdb_id='268592', database='tvdb', item_type='tv')
48866
"""
# according to https://www.themoviedb.org/talk/5f6a0500688cd000351c1712 we can search by imdb id
if database.lower() not in ['imdb', 'tvdb']:
Log.Exception('Invalid database: {}'.format(database))
return

Check warning on line 51 in Contents/Code/tmdb_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/tmdb_helper.py#L50-L51

Added lines #L50 - L51 were not covered by tests
if item_type.lower() not in ['movie', 'tv']:
Log.Exception('Invalid item type: {}'.format(item_type))
return

Check warning on line 54 in Contents/Code/tmdb_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/tmdb_helper.py#L53-L54

Added lines #L53 - L54 were not covered by tests

# according to https://www.themoviedb.org/talk/5f6a0500688cd000351c1712 we can search by external id
# https://api.themoviedb.org/3/find/tt0458290?api_key=###&external_source=imdb_id
find_imdb_item = 'find/{}?external_source=imdb_id'
find_imdb_item = 'find/{}?external_source={}_id'

url = '{}/{}'.format(tmdb_base_url, find_imdb_item.format(String.Quote(s=imdb_id, usePlus=True)))
url = '{}/{}'.format(
tmdb_base_url,
find_imdb_item.format(String.Quote(s=external_id, usePlus=True), database.lower())
)
try:
tmdb_data = JSON.ObjectFromURL(
url=url, sleep=2.0, headers=dict(Accept='application/json'), cacheTime=CACHE_1DAY, errors='strict')
except Exception as e:
Log.Debug('Error converting IMDB ID to TMDB ID: {}'.format(e))
Log.Debug('Error converting external ID to TMDB ID: {}'.format(e))

Check warning on line 68 in Contents/Code/tmdb_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/tmdb_helper.py#L68

Added line #L68 was not covered by tests
else:
Log.Debug('TMDB data: {}'.format(tmdb_data))
try:
tmdb_id = int(tmdb_data['movie_results'][0]['id']) # this is already an integer, but let's force it
# this is already an integer, but let's force it
tmdb_id = int(tmdb_data['{}_results'.format(item_type.lower())][0]['id'])
except (IndexError, KeyError, ValueError):
Log.Debug('Error converting IMDB ID to TMDB ID: {}'.format(tmdb_data))
Log.Debug('Error converting external ID to TMDB ID: {}'.format(tmdb_data))
else:
return tmdb_id

Expand Down
11 changes: 10 additions & 1 deletion Contents/Code/webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,14 @@
or database_id.startswith('tt')
):
# try to get tmdb id from imdb id
tmdb_id = tmdb_helper.get_tmdb_id_from_imdb_id(imdb_id=database_id)
tmdb_id = tmdb_helper.get_tmdb_id_from_external_id(
external_id=database_id, database='imdb', item_type='movie')
database_id = tmdb_id if tmdb_id else None

elif item.type == 'show' and item_agent == 'com.plexapp.agents.tvdb':
ReenigneArcher marked this conversation as resolved.
Show resolved Hide resolved
# try to get tmdb id from tvdb id
tmdb_id = tmdb_helper.get_tmdb_id_from_external_id(

Check warning on line 280 in Contents/Code/webapp.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/webapp.py#L280

Added line #L280 was not covered by tests
external_id=database_id, database='tvdb', item_type='tv')
database_id = tmdb_id if tmdb_id else None

item_issue_url = None
Expand Down Expand Up @@ -300,6 +307,8 @@
database_id = None
else:
issue_title = '{} ({})'.format(getattr(item, "originalTitle", None) or item.title, year)
elif item.type == 'show':
issue_title = '{} ({})'.format(item.title, year)
else: # collections
issue_title = item.title

Expand Down
7 changes: 7 additions & 0 deletions Contents/DefaultPrefs.json
ReenigneArcher marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@
"default": "True",
"secure": "false"
},
{
"id": "bool_auto_update_tv_themes",
"type": "bool",
"label": "Update tv show themes during automatic update",
"default": "True",
"secure": "false"
},
{
"id": "bool_auto_update_collection_themes",
"type": "bool",
Expand Down
9 changes: 9 additions & 0 deletions docs/source/about/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ Description
Default
``True``

Update tv show themes during automatic update
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Description
When enabled, Themerr-plex will update tv show themes during automatic updates.

Default
``True``

Update collection themes during automatic update
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
Loading
Loading