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

Commit

Permalink
feat(shows): add support for tv shows
Browse files Browse the repository at this point in the history
  • Loading branch information
ReenigneArcher committed Jan 22, 2024
1 parent 94e5193 commit 78549e6
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 34 deletions.
34 changes: 25 additions & 9 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

def __init__(self, *args, **kwargs):
super(Themerr, self).__init__(*args, **kwargs)

Check warning on line 274 in Contents/Code/__init__.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/__init__.py#L274

Added line #L274 was not covered by tests

@staticmethod
def search(results, media, lang, manual):
# type: (SearchResult, Media.Movie, str, bool) -> Optional[SearchResult]
# 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 @@ -339,7 +342,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 +354,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 +366,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 +383,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"
14 changes: 9 additions & 5 deletions Contents/Code/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@
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
'com.plexapp.agents.imdb', # legacy movie agent
'com.plexapp.agents.themoviedb', # movie and tv show agent
'com.plexapp.agents.thetvdb', # tv show agent
'dev.lizardbyte.retroarcher-plex' # retroarcher plugin
]

guid_map = dict(
Expand Down Expand Up @@ -73,13 +73,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 +99,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 @@ def get_database_info(item):
database = 'imdb'
database_id = item.guid.split('://')[1].split('?')[0]

elif item.type == 'show':
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]

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/plex_api_helper.py#L506-L516

Added lines #L506 - L516 were not covered by tests

# 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(

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/plex_api_helper.py#L519

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

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/plex_api_helper.py#L524

Added line #L524 was not covered by tests

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 @@ def scheduled_update():
Log.Debug('Themerr-plex is disabled for agent "{}"'.format(section.agent))
continue

all_items = []

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/plex_api_helper.py#L738

Added line #L738 was not covered by tests

# 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 []

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/plex_api_helper.py#L741-L742

Added lines #L741 - L742 were not covered by tests

# 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 []

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/plex_api_helper.py#L745

Added line #L745 was not covered by tests

# 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#L749-L751

Added lines #L749 - L751 were not covered by tests

for item in all_items:
if item.ratingKey not in q.queue:
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 @@ def get_tmdb_id_from_imdb_id(imdb_id):
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
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#L49-L54

Added lines #L49 - 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'

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/tmdb_helper.py#L58

Added line #L58 was not covered by tests

url = '{}/{}'.format(tmdb_base_url, find_imdb_item.format(String.Quote(s=imdb_id, usePlus=True)))
url = '{}/{}'.format(

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/tmdb_helper.py#L60

Added line #L60 was not covered by tests
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'])

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/tmdb_helper.py#L73

Added line #L73 was not covered by tests
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))

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/tmdb_helper.py#L75

Added line #L75 was not covered by tests
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 @@ def cache_data():
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(

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/webapp.py#L274

Added line #L274 was not covered by tests
external_id=database_id, database='imdb', item_type='movie')
database_id = tmdb_id if tmdb_id else None

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/webapp.py#L276

Added line #L276 was not covered by tests

elif item.type == 'show' and item_agent == 'com.plexapp.agents.tvdb':

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/webapp.py#L278

Added line #L278 was not covered by tests
# 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 @@ def cache_data():
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)

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/webapp.py#L310-L311

Added lines #L310 - L311 were not covered by tests
else: # collections
issue_title = item.title

Expand Down
7 changes: 7 additions & 0 deletions Contents/DefaultPrefs.json
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
4 changes: 2 additions & 2 deletions tests/unit/test_tmdb_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ def test_get_tmdb_id_from_imdb_id(tmdb_test_id):
print(plexhints.CONTENTS)
print(plexhints.ELEVATED_POLICY)

tmdb_id = tmdb_helper.get_tmdb_id_from_imdb_id(imdb_id=tmdb_test_id)
tmdb_id = tmdb_helper.get_tmdb_id_from_external_id(external_id=tmdb_test_id, database='imdb', item_type='movie')
assert tmdb_id, "No tmdb_id found for {}".format(tmdb_test_id)
assert isinstance(tmdb_id, int), "tmdb_id is not an int: {}".format(tmdb_id)


def test_get_tmdb_id_from_imdb_id_invalid():
test = tmdb_helper.get_tmdb_id_from_imdb_id(imdb_id='invalid')
test = tmdb_helper.get_tmdb_id_from_external_id(external_id='invalid', database='imdb', item_type='movie')
assert test is None, "tmdb_id found for invalid imdb_id: {}".format(test)


Expand Down

0 comments on commit 78549e6

Please sign in to comment.