diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index a4bbd743..79d37e1a 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1008,17 +1008,102 @@ a .summary-poster-face-episode:hover { background-size: contain; height: 16px; } +.show-seasons-wrapper { +} +.show-seasons-instance { + list-style: none; + margin: 0; +} +.show-seasons-instance li { + float: left; + position: relative; + left: 0px; + margin-right: 25px; + margin-bottom: 25px; +} +a .show-seasons-card-overlay:hover { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; +} +.show-seasons-poster { + float: left; + position: relative; + left: 0px; +} +.show-seasons-card-overlay { + position: absolute; + left: 0; + right: 0; + bottom: 0; + text-align: left; + background: -moz-linear-gradient(top, rgba(0,0,0,0) 30%, rgba(0,0,0,1) 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(30%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); + background: -o-linear-gradient(top, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); + background: -ms-linear-gradient(top, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); + background: linear-gradient(to bottom, rgba(0,0,0,0) 30%,rgba(0,0,0,1) 100%); + height: 225px; +} +.show-seasons-overlay-text { + color: #aaa; + font-size: 12px; + float: left; + position: absolute; + left: 8px; + bottom: 5px; +} +.show-seasons-instance-text-wrapper { + width: 150px; + font-size: 13px; + margin-bottom: 20px; + clear: both; +} +.show-seasons-instance-text-wrapper h3 { + padding: 5px 3px 0 3px; + color: #fff; + text-overflow: ellipsis; + overflow: hidden; + position: relative; + font-size: 13px; + margin: 0; + line-height: 15px; + font-weight: normal; + width: 250px; + white-space: nowrap; + text-align: left; + clear: both; +} +.show-seasons-title a { + text-decoration: none; + font-size: 14px; + font-weight: normal; + color: #fff; + float: left; + text-overflow: ellipsis; + overflow: hidden; + position: relative; + white-space: nowrap; + width: 205px; + margin-top: 2px; + margin-left: 0px; + margin-bottom: 20px; +} +.show-seasons a:hover { + color: #F9AA03; +} .season-episodes-wrapper { } .season-episodes-instance { list-style: none; - margin: 0 0 0px 0px; + margin: 0; } .season-episodes-instance li { float: left; position: relative; left: 0px; margin-right: 25px; + margin-bottom: 25px; } a .season-episodes-card-overlay:hover { webkit-box-shadow: inset 0 0 0 2px #e9a049; @@ -1059,11 +1144,13 @@ a .season-episodes-card-overlay:hover { font-size: 11px; text-shadow: 0 1px 5px rgba(0,0,0,0.2); } -.season-episodes-instance-text-wrapper { - width: 250px; - font-size: 13px; - margin-bottom: 20px; - clear: both; +.season-episodes-overlay-text { + color: #aaa; + font-size: 12px; + float: left; + position: absolute; + left: 8px; + bottom: 5px; } .season-episodes-instance-text-wrapper h3 { padding: 5px 3px 0 3px; @@ -1098,14 +1185,6 @@ a .season-episodes-card-overlay:hover { .season-episodes a:hover { color: #F9AA03; } -.season-episodes-season { - color: #aaa; - font-size: 12px; - float: left; - position: absolute; - left: 8px; - bottom: 5px; -} .user-info-wrapper { height: 113px; } diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 078d1670..c2cacf2a 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -197,7 +197,30 @@ DOCUMENTATION :: END % endif - % if data['type'] == 'movie' or data['type'] == 'episode' or data['type'] == 'show': + % if data['type'] == 'show': +
+
+
+ Season List for ${data['title']} +
+
+
+
+
+
+ % elif data['type'] == 'season': +
+
+
+ Episode List for ${data['title']} +
+
+
+
+
+
+ % endif + % if data['type'] == 'movie' or data['type'] == 'episode' or data['type'] == 'show' or data['type'] == 'season':
@@ -233,18 +256,7 @@ DOCUMENTATION :: END
- % elif data['type'] == 'season': -
-
-
- Episode List for ${data['title']} -
-
-
-
-
- % endif -
+ % endif
@@ -288,28 +300,27 @@ DOCUMENTATION :: END type: 'post', data: function ( d ) { return { 'json_data': JSON.stringify( d ), - 'rating_key': ${data['rating_key']} - }; - } - } + 'rating_key': ${data['rating_key']} }; + } + } history_table = $('#history_table').DataTable(history_table_options); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); - $(colvis.button()).appendTo('div.colvis-button-bar'); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + $(colvis.button()).appendTo('div.colvis-button-bar'); - clearSearchButton('history_table', history_table); + clearSearchButton('history_table', history_table); - $('#row-edit-mode').click(function() { - if ($(this).hasClass('active')) { - $('.delete-control').each(function() { - $(this).addClass('hidden'); - }); - } else { - $('.delete-control').each(function() { - $(this).removeClass('hidden'); - }); - } - }); + $('#row-edit-mode').click(function() { + if ($(this).hasClass('active')) { + $('.delete-control').each(function() { + $(this).addClass('hidden'); + }); + } else { + $('.delete-control').each(function() { + $(this).removeClass('hidden'); + }); + } + }); }); % elif data['type'] == 'show': @@ -321,40 +332,75 @@ DOCUMENTATION :: END type: 'post', data: function ( d ) { return { 'json_data': JSON.stringify( d ), - 'grandparent_rating_key': ${data['rating_key']} - }; - } - } - history_table = $('#history_table').DataTable(history_table_options); - var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); - $(colvis.button()).appendTo('div.colvis-button-bar'); - - clearSearchButton('history_table', history_table); - - $('#row-edit-mode').click(function() { - if ($(this).hasClass('active')) { - $('.delete-control').each(function() { - $(this).addClass('hidden'); - }); - } else { - $('.delete-control').each(function() { - $(this).removeClass('hidden'); - }); + 'grandparent_rating_key': ${data['rating_key']} }; + } } + history_table = $('#history_table').DataTable(history_table_options); + var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 10] }); + $(colvis.button()).appendTo('div.colvis-button-bar'); + + clearSearchButton('history_table', history_table); + + $('#row-edit-mode').click(function() { + if ($(this).hasClass('active')) { + $('.delete-control').each(function() { + $(this).addClass('hidden'); + }); + } else { + $('.delete-control').each(function() { + $(this).removeClass('hidden'); + }); + } + }); }); + $.ajax({ + url: 'get_show_children', + type: "GET", + async: true, + data: { rating_key : ${data['rating_key']} }, + complete: function(xhr, status) { + $("#season-list").html(xhr.responseText); } }); % endif % if data['type'] == 'season': + % endif diff --git a/data/interfaces/default/info_episode_list.html b/data/interfaces/default/info_episode_list.html index ef02c42c..11651969 100644 --- a/data/interfaces/default/info_episode_list.html +++ b/data/interfaces/default/info_episode_list.html @@ -34,7 +34,7 @@ DOCUMENTATION :: END
-
+
Episode ${a['index']}
diff --git a/data/interfaces/default/info_season_list.html b/data/interfaces/default/info_season_list.html new file mode 100644 index 00000000..6da68e0e --- /dev/null +++ b/data/interfaces/default/info_season_list.html @@ -0,0 +1,55 @@ +<%doc> +USAGE DOCUMENTATION :: PLEASE LEAVE THIS AT THE TOP OF THIS FILE + +For Mako templating syntax documentation please visit: http://docs.makotemplates.org/en/latest/ + +Filename: info_season_list.html +Version: 0.1 +Variable names: data [list] + +data :: Usable parameters + +== Global keys == +season_count Returns the number of seasons in the array. +season_list Returns an array of seasons. + +data['season_list'] :: Usable paramaters + +== Global keys == +rating_key Returns the unique identifier for the media item. +thumb Returns the location of the item's thumbnail. Use with pms_image_proxy. +title Returns the name of the season. +index Returns the season number. + +DOCUMENTATION :: END + + +% if data != None: +% if data['season_count'] > 0: +
+ +
+% endif +% endif \ No newline at end of file diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index b60c654b..e052a1f7 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -55,6 +55,7 @@ class DataFactory(object): (CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \ session_history_metadata.duration * 1.0 END) * 100) as percent_complete', 'session_history.grandparent_rating_key as grandparent_rating_key', + 'session_history.parent_rating_key as parent_rating_key', 'session_history.rating_key as rating_key', 'session_history.user', 'session_history_metadata.media_type', @@ -112,6 +113,7 @@ class DataFactory(object): "duration": item["duration"], "percent_complete": item["percent_complete"], "grandparent_rating_key": item["grandparent_rating_key"], + "parent_rating_key": item["parent_rating_key"], "rating_key": item["rating_key"], "user": item["user"], "media_type": item["media_type"], diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 7fb15618..dba5b0b8 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -89,6 +89,23 @@ class PmsConnect(object): return request + """ + Return list of seasons in requested show. + + Parameters required: rating_key { ratingKey of parent } + Optional parameters: output_format { dict, json } + + Output: array + """ + def get_season_list(self, rating_key='', output_format=''): + uri = '/library/metadata/' + rating_key + '/children' + request = self.request_handler.make_request(uri=uri, + proto=self.protocol, + request_type='GET', + output_format=output_format) + + return request + """ Return list of episodes in requested season. @@ -103,7 +120,7 @@ class PmsConnect(object): proto=self.protocol, request_type='GET', output_format=output_format) - + return request """ @@ -798,6 +815,49 @@ class PmsConnect(object): return session_output + """ + Return processed and validated season list. + + Output: array + """ + def get_show_children(self, rating_key=''): + season_data = self.get_season_list(rating_key, output_format='xml') + + try: + xml_head = season_data.getElementsByTagName('MediaContainer') + except: + logger.warn("Unable to parse XML for get_season_list.") + return [] + + season_list = [] + + for a in xml_head: + if a.getAttribute('size'): + if a.getAttribute('size') == '0': + logger.debug(u"No season data.") + episode_list = {'season_count': '0', + 'season_list': [] + } + return season_list + + if a.getElementsByTagName('Directory'): + result_data = a.getElementsByTagName('Directory') + for result in result_data: + season_output = {'rating_key': helpers.get_xml_attr(result, 'ratingKey'), + 'index': helpers.get_xml_attr(result, 'index'), + 'title': helpers.get_xml_attr(result, 'title'), + 'thumb': helpers.get_xml_attr(result, 'thumb'), + 'parent_thumb': helpers.get_xml_attr(a, 'thumb') + } + season_list.append(season_output) + + output = {'season_count': helpers.get_xml_attr(xml_head[0], 'size'), + 'title': helpers.get_xml_attr(xml_head[0], 'title2'), + 'season_list': season_list + } + + return output + """ Return processed and validated episode list. diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 54fc8d9f..92c378d0 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -1,4 +1,4 @@ -# This file is part of PlexPy. +# This file is part of PlexPy. # # PlexPy is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -567,6 +567,9 @@ class WebInterface(object): if 'rating_key' in kwargs: rating_key = kwargs.get('rating_key', "") custom_where = [['rating_key', rating_key]] + if 'parent_rating_key' in kwargs: + rating_key = kwargs.get('parent_rating_key', "") + custom_where = [['parent_rating_key', rating_key]] if 'grandparent_rating_key' in kwargs: rating_key = kwargs.get('grandparent_rating_key', "") custom_where = [['grandparent_rating_key', rating_key]] @@ -804,7 +807,19 @@ class WebInterface(object): return serve_template(templatename="user_platform_stats.html", data=None, title="Platform Stats") @cherrypy.expose - def get_children(self, rating_key='', **kwargs): + def get_show_children(self, rating_key='', **kwargs): + + pms_connect = pmsconnect.PmsConnect() + result = pms_connect.get_show_children(rating_key) + + if result: + return serve_template(templatename="info_season_list.html", data=result, title="Season List") + else: + logger.warn('Unable to retrieve data.') + return serve_template(templatename="info_season_list.html", data=None, title="Season List") + + @cherrypy.expose + def get_season_children(self, rating_key='', **kwargs): pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_season_children(rating_key)