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 15 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@v2024.127.5219
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.thetvdb/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
48 changes: 34 additions & 14 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 @@ -195,7 +195,7 @@
start_queue_threads() # start queue threads
Log.Debug('queue threads started.')

if Prefs['bool_plex_movie_support']:
if Prefs['bool_plex_movie_support'] or Prefs['bool_plex_series_support']:

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

View check run for this annotation

Codecov / codecov/patch

Contents/Code/__init__.py#L198

Added line #L198 was not covered by tests
plex_listener() # start watching plex
Log.Debug('plex_listener started, watching for activity from new Plex Movie agent.')

Expand All @@ -216,9 +216,9 @@
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 @@
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 @@
----------
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 @@
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 @@

@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 @@

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 @@
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 @@
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
3 changes: 3 additions & 0 deletions Contents/Code/default_prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

default_prefs = dict(
bool_plex_movie_support='True',
bool_plex_series_support='True',
bool_overwrite_plex_provided_themes='False',
bool_prefer_mp4a_codec='True',
bool_remove_unused_theme_songs='True',
bool_remove_unused_art='False',
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
105 changes: 90 additions & 15 deletions Contents/Code/general_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
from plexhints.log_kit import Log # log kit
from plexhints.prefs_kit import Prefs # prefs kit

# imports from Libraries\Shared
from plexapi.base import PlexPartialObject
from typing import Optional

# local imports
from constants import metadata_base_directory, metadata_type_map, themerr_data_directory
Expand All @@ -26,16 +29,46 @@
]


def _get_metadata_path(item):
# type: (PlexPartialObject) -> str
"""
Get the metadata path of the item.

Get the hashed path of the metadata directory for the item specified by the ``item``.

Parameters
----------
item : PlexPartialObject
The item to get the theme upload path for.

Returns
-------
str
The path to the metadata directory.

Examples
--------
>>> _get_metadata_path(item=...)
"...bundle"
"""
guid = item.guid
full_hash = hashlib.sha1(guid).hexdigest()
metadata_path = os.path.join(
metadata_base_directory, metadata_type_map[item.type],
full_hash[0], full_hash[1:] + '.bundle')
return metadata_path


def get_media_upload_path(item, media_type):
# type: (any, str) -> str
# type: (PlexPartialObject, str) -> str
"""
Get the path to the theme upload directory.

Get the hashed path of the theme upload directory for the item specified by the ``item``.

Parameters
----------
item : any
item : PlexPartialObject
The item to get the theme upload path for.
media_type : str
The media type to get the theme upload path for. Must be one of 'art', 'posters', or 'themes'.
Expand Down Expand Up @@ -66,24 +99,66 @@
'media_type must be one of: {}'.format(allowed_media_types)
)

guid = item.guid
full_hash = hashlib.sha1(guid).hexdigest()
theme_upload_path = os.path.join(
metadata_base_directory, metadata_type_map[item.type],
full_hash[0], full_hash[1:] + '.bundle', 'Uploads', media_type)
theme_upload_path = os.path.join(_get_metadata_path(item=item), 'Uploads', media_type)
return theme_upload_path


def get_theme_provider(item):
# type: (PlexPartialObject) -> Optional[str]
"""
Get the theme provider.

Get the theme provider for the item specified by the ``item``.

Parameters
----------
item : PlexPartialObject
The item to get the theme provider for.

Returns
-------
str
The theme provider.

Examples
--------
>>> get_theme_provider(item=...)
...
"""
provider_map = {
'local': 'user',
'com.plexapp.agents.plexthememusic': 'plex',
ReenigneArcher marked this conversation as resolved.
Show resolved Hide resolved
'tv.plex.agents.movies': 'plex',
'tv.plex.agents.series': 'plex',
}

if not item.themes():
return

Check warning on line 136 in Contents/Code/general_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/general_helper.py#L136

Added line #L136 was not covered by tests

selected = (theme for theme in item.themes() if theme.selected).next()

if selected.provider in provider_map.keys():
provider = provider_map[selected.provider]

Check warning on line 141 in Contents/Code/general_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/general_helper.py#L141

Added line #L141 was not covered by tests
else:
provider = selected.provider

if not provider:
themerr_data = get_themerr_json_data(item=item)
provider = 'themerr' if themerr_data else None

return provider


def get_themerr_json_path(item):
# type: (any) -> str
# type: (PlexPartialObject) -> str
"""
Get the path to the Themerr data file.

Get the path to the Themerr data file for the item specified by the ``item``.

Parameters
----------
item : any
item : PlexPartialObject
The item to get the Themerr data file path for.

Returns
Expand All @@ -102,7 +177,7 @@


def get_themerr_json_data(item):
# type: (any) -> dict
# type: (PlexPartialObject) -> dict
"""
Get the Themerr data for the specified item.

Expand All @@ -111,7 +186,7 @@

Parameters
----------
item : any
item : PlexPartialObject
The item to get the Themerr data for.

Returns
Expand Down Expand Up @@ -154,15 +229,15 @@


def remove_uploaded_media(item, media_type):
# type: (any, str) -> None
# type: (PlexPartialObject, str) -> None
"""
Remove themes for the specified item.

Deletes the themes upload directory for the item specified by the ``item``.

Parameters
----------
item : any
item : PlexPartialObject
The item to remove the themes from.
media_type : str
The media type to remove the themes from. Must be one of 'art', 'posters', or 'themes'.
Expand Down Expand Up @@ -202,15 +277,15 @@


def update_themerr_data_file(item, new_themerr_data):
# type: (any, dict) -> None
# type: (PlexPartialObject, dict) -> None
"""
Update the Themerr data file for the specified item.

This updates the themerr data file after uploading media to the Plex server.

Parameters
----------
item : any
item : PlexPartialObject
The item to update the Themerr data file for.
new_themerr_data : dict
The Themerr data to update the Themerr data file with.
Expand Down
Loading
Loading