diff --git a/API.md b/API.md index ecdc127b..427dbc6b 100644 --- a/API.md +++ b/API.md @@ -1,10 +1,11 @@ # API Reference + The API is still pretty new and needs some serious cleaning up on the backend but should be reasonably functional. There are no error codes yet. ## General structure -The API endpoint is `http://ip:port + HTTP_ROOT + /api?apikey=$apikey&cmd=$command` +The API endpoint is `http://ip:port + HTTP_ROOT + /api/v2?apikey=$apikey&cmd=$command` -Response example +Response example (default `json`) ``` { "response": { @@ -21,60 +22,1620 @@ Response example } } ``` +``` +General optional parameters: -General parameters: - out_type: 'xml', - callback: 'pong', - 'debug': 1 - + out_type: "json" or "xml" + callback: "pong" + debug: 1 +``` ## API methods -### getLogs -Possible params: sort='', search='', order='desc', regex='', start=0, end=0 -Returns the plexpy log +### arnold +Get to the chopper! -### getApikey -Possible params: username='', password='' (required if auth is enabled) -Returns the apikey -### getSettings -No params -Returns the config file +### backup_config +Create a manual backup of the `config.ini` file. -### getVersion -No params -Returns some version information: git_path, install_type, current_version, installed_version, commits_behind -### getHistory -possible params: user=None, user_id=None, ,rating_key='', parent_rating_key='', grandparent_rating_key='', start_date='' -Returns +### backup_db +Create a manual backup of the `plexpy.db` file. -### getMetadata -Required params: rating_key -Returns metadata about a file -### getSync -Possible params: machine_id=None, user_id=None, -Returns +### delete_all_library_history +Delete all PlexPy history for a specific library. -### getUserips -Possible params: user_id=None, user=None +``` +Required parameters: + section_id (str): The id of the Plex library section -### getPlayby -Possible params: time_range=30, y_axis='plays', playtype='total_plays_per_month' +Optional parameters: + None -### checkGithub -Updates the version information above and returns getVersion data +Returns: + None +``` + + +### delete_all_user_history +Delete all PlexPy history for a specific user. + +``` +Required parameters: + user_id (str): The id of the Plex user + +Optional parameters: + None + +Returns: + None +``` + + +### delete_cache +Delete and recreate the cache directory. + + +### delete_datatable_media_info_cache +Delete the media info table cache for a specific library. + +``` +Required parameters: + section_id (str): The id of the Plex library section + +Optional parameters: + None + +Returns: + None +``` + + +### delete_image_cache +Delete and recreate the image cache directory. + + +### delete_library +Delete a library section from PlexPy. Also erases all history for the library. + +``` +Required parameters: + section_id (str): The id of the Plex library section + +Optional parameters: + None + +Returns: + None +``` + + +### delete_login_log +Delete the PlexPy login logs. + +``` +Required paramters: + None + +Optional parameters: + None + +Returns: + None +``` + + +### delete_notification_log +Delete the PlexPy notification logs. + +``` +Required paramters: + None + +Optional parameters: + None + +Returns: + None +``` + + +### delete_user +Delete a user from PlexPy. Also erases all history for the user. + +``` +Required parameters: + user_id (str): The id of the Plex user + +Optional parameters: + None + +Returns: + None +``` + + +### docs +Return the api docs as a dict where commands are keys, docstring are value. + + +### docs_md +Return the api docs formatted with markdown. + + +### download_log +Download the PlexPy log file. + + +### edit_library +Update a library section on PlexPy. + +``` +Required parameters: + section_id (str): The id of the Plex library section + +Optional parameters: + custom_thumb (str): The URL for the custom library thumbnail + do_notify (int): 0 or 1 + do_notify_created (int): 0 or 1 + keep_history (int): 0 or 1 + +Returns: + None +``` + + +### edit_user +Update a user on PlexPy. + +``` +Required parameters: + user_id (str): The id of the Plex user + +Optional paramters: + friendly_name(str): The friendly name of the user + custom_thumb (str): The URL for the custom user thumbnail + do_notify (int): 0 or 1 + do_notify_created (int): 0 or 1 + keep_history (int): 0 or 1 + +Returns: + None +``` + + +### get_activity +Get the current activity on the PMS. + +``` +Required parameters: + None + +Optional parameters: + None + +Returns: + json: + {"stream_count": 3, + "session": + [{"art": "/library/metadata/1219/art/1462175063", + "aspect_ratio": "1.78", + "audio_channels": "6", + "audio_codec": "ac3", + "audio_decision": "transcode", + "bif_thumb": "/library/parts/274169/indexes/sd/", + "bitrate": "10617", + "container": "mkv", + "content_rating": "TV-MA", + "duration": "2998290", + "friendly_name": "Mother of Dragons", + "grandparent_rating_key": "1219", + "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", + "grandparent_title": "Game of Thrones", + "height": "1078", + "indexes": 1, + "ip_address": "xxx.xxx.xxx.xxx", + "labels": [], + "machine_id": "83f189w617623ccs6a1lqpby", + "media_index": "1", + "media_type": "episode", + "parent_media_index": "6", + "parent_rating_key": "153036", + "parent_thumb": "/library/metadata/153036/thumb/1462175062", + "parent_title": "", + "platform": "Chrome", + "player": "Plex Web (Chrome)", + "progress_percent": "0", + "rating_key": "153037", + "section_id": "2", + "session_key": "291", + "state": "playing", + "throttled": "1", + "thumb": "/library/metadata/153037/thumb/1462175060", + "title": "The Red Woman", + "transcode_audio_channels": "2", + "transcode_audio_codec": "aac", + "transcode_container": "mkv", + "transcode_height": "1078", + "transcode_key": "tiv5p524wcupe8nxegc26s9k9", + "transcode_progress": 2, + "transcode_protocol": "http", + "transcode_speed": "0.0", + "transcode_video_codec": "h264", + "transcode_width": "1920", + "user": "DanyKhaleesi69", + "user_id": 8008135, + "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar", + "video_codec": "h264", + "video_decision": "copy", + "video_framerate": "24p", + "video_resolution": "1080", + "view_offset": "", + "width": "1920", + "year": "2016" + }, + {...}, + {...} + ] + } +``` + + +### get_apikey +Get the apikey. Username and password are required +if auth is enabled. Makes and saves the apikey if it does not exist. + +``` +Required parameters: + None + +Optional parameters: + username (str): Your PlexPy username + password (str): Your PlexPy password + +Returns: + string: "apikey" +``` + + +### get_date_formats +Get the date and time formats used by PlexPy. + + ``` +Required parameters: + None + +Optional parameters: + None + +Returns: + json: + {"date_format": "YYYY-MM-DD", + "time_format": "HH:mm", + } +``` + + +### get_history +Get the PlexPy history. + +``` +Required parameters: + None + +Optional parameters: + grouping (int): 0 or 1 + user (str): "Jon Snow" + user_id (int): 133788 + rating_key (int): 4348 + parent_rating_key (int): 544 + grandparent_rating_key (int): 351 + start_date (str): "YYYY-MM-DD" + section_id (int): 2 + media_type (str): "movie", "episode", "track" + transcode_decision (str): "direct play", "copy", "transcode", + order_column (str): "date", "friendly_name", "ip_address", "platform", "player", + "full_title", "started", "paused_counter", "stopped", "duration" + order_dir (str): "desc" or "asc" + start (int): Row to start from, 0 + length (int): Number of items to return, 25 + search (str): A string to search for, "Thrones" + +Returns: + json: + {"draw": 1, + "recordsTotal": 1000, + "recordsFiltered": 250, + "total_duration": "42 days 5 hrs 18 mins", + "filter_duration": "10 hrs 12 mins", + "data": + [{"year": 2016, + "paused_counter": 0, + "player": "Plex Web (Chrome)", + "parent_rating_key": 544, + "parent_title": "", + "duration": 263, + "transcode_decision": "transcode", + "rating_key": 4348, + "user_id": 8008135, + "thumb": "/library/metadata/4348/thumb/1462414561", + "id": 1124, + "platform": "Chrome", + "media_type": "episode", + "grandparent_rating_key": 351, + "started": 1462688107, + "full_title": "Game of Thrones - The Red Woman", + "reference_id": 1123, + "date": 1462687607, + "percent_complete": 84, + "ip_address": "xxx.xxx.xxx.xxx", + "group_ids": "1124", + "media_index": 17, + "friendly_name": "Mother of Dragons", + "watched_status": 0, + "group_count": 1, + "stopped": 1462688370, + "parent_media_index": 7, + "user": "DanyKhaleesi69" + }, + {...}, + {...} + ] + } +``` + + +### get_home_stats +Get the homepage watch statistics. + +``` +Required parameters: + None + +Optional parameters: + grouping (int): 0 or 1 + time_range (str): The time range to calculate statistics, '30' + stats_type (int): 0 for plays, 1 for duration + stats_count (str): The number of top items to list, '5' + +Returns: + json: + [{"stat_id": "top_movies", + "stat_type": "total_plays", + "rows": [{...}] + }, + {"stat_id": "popular_movies", + "rows": [{...}] + }, + {"stat_id": "top_tv", + "stat_type": "total_plays", + "rows": + [{"content_rating": "TV-MA", + "friendly_name": "", + "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", + "labels": [], + "last_play": 1462380698, + "media_type": "episode", + "platform": "", + "platform_type": "", + "rating_key": 1219, + "row_id": 1116, + "section_id": 2, + "thumb": "", + "title": "Game of Thrones", + "total_duration": 213302, + "total_plays": 69, + "user": "", + "users_watched": "" + }, + {...}, + {...} + ] + }, + {"stat_id": "popular_tv", + "rows": [{...}] + }, + {"stat_id": "top_music", + "stat_type": "total_plays", + "rows": [{...}] + }, + {"stat_id": "popular_music", + "rows": [{...}] + }, + {"stat_id": "last_watched", + "rows": [{...}] + }, + {"stat_id": "top_users", + "stat_type": "total_plays", + "rows": [{...}] + }, + {"stat_id": "top_platforms", + "stat_type": "total_plays", + "rows": [{...}] + }, + {"stat_id": "most_concurrent", + "rows": [{...}] + } + ] +``` + + +### get_libraries +Get a list of all libraries on your server. + +``` +Required parameters: + None + +Optional parameters: + None + +Returns: + json: + [{"art": "/:/resources/show-fanart.jpg", + "child_count": "3745", + "count": "62", + "parent_count": "240", + "section_id": "2", + "section_name": "TV Shows", + "section_type": "show", + "thumb": "/:/resources/show.png" + }, + {...}, + {...} + ] +``` + + +### get_libraries_table +Get the data on the PlexPy libraries table. + +``` +Required parameters: + None + +Optional parameters: + order_column (str): "library_thumb", "section_name", "section_type", "count", "parent_count", + "child_count", "last_accessed", "last_played", "plays", "duration" + order_dir (str): "desc" or "asc" + start (int): Row to start from, 0 + length (int): Number of items to return, 25 + search (str): A string to search for, "Movies" + +Returns: + json: + {"draw": 1, + "recordsTotal": 10, + "recordsFiltered": 10, + "data": + [{"child_count": 3745, + "content_rating": "TV-MA", + "count": 62, + "do_notify": "Checked", + "do_notify_created": "Checked", + "duration": 1578037, + "id": 1128, + "keep_history": "Checked", + "labels": [], + "last_accessed": 1462693216, + "last_played": "Game of Thrones - The Red Woman", + "library_art": "/:/resources/show-fanart.jpg", + "library_thumb": "", + "media_index": 1, + "media_type": "episode", + "parent_count": 240, + "parent_media_index": 6, + "parent_title": "", + "plays": 772, + "rating_key": 153037, + "section_id": 2, + "section_name": "TV Shows", + "section_type": "Show", + "thumb": "/library/metadata/153036/thumb/1462175062", + "year": 2016 + }, + {...}, + {...} + ] + } +``` + + +### get_library_media_info +Get the data on the PlexPy media info tables. + +``` +Required parameters: + section_id (str): The id of the Plex library section, OR + rating_key (str): The grandparent or parent rating key + +Optional parameters: + section_type (str): "movie", "show", "artist", "photo" + order_column (str): "added_at", "title", "container", "bitrate", "video_codec", + "video_resolution", "video_framerate", "audio_codec", "audio_channels", + "file_size", "last_played", "play_count" + order_dir (str): "desc" or "asc" + start (int): Row to start from, 0 + length (int): Number of items to return, 25 + search (str): A string to search for, "Thrones" + +Returns: + json: + {"draw": 1, + "recordsTotal": 82, + "recordsFiltered": 82, + "filtered_file_size": 2616760056742, + "total_file_size": 2616760056742, + "data": + [{"added_at": "1403553078", + "audio_channels": "", + "audio_codec": "", + "bitrate": "", + "container": "", + "file_size": 253660175293, + "grandparent_rating_key": "", + "last_played": 1462380698, + "media_index": "1", + "media_type": "show", + "parent_media_index": "", + "parent_rating_key": "", + "play_count": 15, + "rating_key": "1219", + "section_id": 2, + "section_type": "show", + "thumb": "/library/metadata/1219/thumb/1436265995", + "title": "Game of Thrones", + "video_codec": "", + "video_framerate": "", + "video_resolution": "", + "year": "2011" + }, + {...}, + {...} + ] + } +``` + + +### get_library_names +Get a list of library sections and ids on the PMS. + +``` +Required parameters: + None + +Optional parameters: + None + +Returns: + json: + [{"section_id": 1, "section_name": "Movies"}, + {"section_id": 7, "section_name": "Music"}, + {"section_id": 2, "section_name": "TV Shows"}, + {...} + ] +``` + + +### get_logs +Get the PlexPy logs. + +``` +Required parameters: + None + +Optional parameters: + sort (str): "time", "thread", "msg", "loglevel" + search (str): A string to search for + order (str): "desc" or "asc" + regex (str): A regex string to search for + start (int): Row number to start from + end (int): Row number to end at + +Returns: + json: + [{"loglevel": "DEBUG", + "msg": "Latest version is 2d10b0748c7fa2ee4cf59960c3d3fffc6aa9512b", + "thread": "MainThread", + "time": "2016-05-08 09:36:51 " + }, + {...}, + {...} + ] +``` + + +### get_metadata +Get the metadata for a media item. + +``` +Required parameters: + rating_key (str): Rating key of the item + media_info (bool): True or False wheter to get media info + +Optional parameters: + None + +Returns: + json: + {"metadata": + {"actors": [ + "Kit Harington", + "Emilia Clarke", + "Isaac Hempstead-Wright", + "Maisie Williams", + "Liam Cunningham", + ], + "added_at": "1461572396", + "art": "/library/metadata/1219/art/1462175063", + "content_rating": "TV-MA", + "directors": [ + "Jeremy Podeswa" + ], + "duration": "2998290", + "genres": [ + "Adventure", + "Drama", + "Fantasy" + ], + "grandparent_rating_key": "1219", + "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", + "grandparent_title": "Game of Thrones", + "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en", + "labels": [], + "last_viewed_at": "1462165717", + "library_name": "TV Shows", + "media_index": "1", + "media_type": "episode", + "originally_available_at": "2016-04-24", + "parent_media_index": "6", + "parent_rating_key": "153036", + "parent_thumb": "/library/metadata/153036/thumb/1462175062", + "parent_title": "", + "rating": "7.8", + "rating_key": "153037", + "section_id": "2", + "studio": "HBO", + "summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.", + "tagline": "", + "thumb": "/library/metadata/153037/thumb/1462175060", + "title": "The Red Woman", + "updated_at": "1462175060", + "writers": [ + "David Benioff", + "D. B. Weiss" + ], + "year": "2016" + } + } +``` + + +### get_new_rating_keys +Get a list of new rating keys for the PMS of all of the item's parent/children. + +``` +Required parameters: + rating_key (str): '12345' + media_type (str): "movie", "show", "season", "episode", "artist", "album", "track" + +Optional parameters: + None + +Returns: + json: + {} +``` + + +### get_notification_log +Get the data on the PlexPy notification logs table. + +``` +Required parameters: + None + +Optional parameters: + order_column (str): "timestamp", "agent_name", "notify_action", + "subject_text", "body_text", "script_args" + order_dir (str): "desc" or "asc" + start (int): Row to start from, 0 + length (int): Number of items to return, 25 + search (str): A string to search for, "Telegram" + +Returns: + json: + {"draw": 1, + "recordsTotal": 1039, + "recordsFiltered": 163, + "data": + [{"agent_id": 13, + "agent_name": "Telegram", + "body_text": "Game of Thrones - S06E01 - The Red Woman [Transcode].", + "id": 1000, + "notify_action": "play", + "poster_url": "http://i.imgur.com/ZSqS8Ri.jpg", + "rating_key": 153037, + "script_args": "[]", + "session_key": 147, + "subject_text": "PlexPy (Winterfell-Server)", + "timestamp": 1462253821, + "user": "DanyKhaleesi69", + "user_id": 8008135 + }, + {...}, + {...} + ] + } +``` + + +### get_old_rating_keys +Get a list of old rating keys from the PlexPy database for all of the item's parent/children. + +``` +Required parameters: + rating_key (str): '12345' + media_type (str): "movie", "show", "season", "episode", "artist", "album", "track" + +Optional parameters: + None + +Returns: + json: + {} +``` + + +### get_plays_by_date +Get graph data by date. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["YYYY-MM-DD", "YYYY-MM-DD", ...] + "series": + [{"name": "Movies", "data": [...]} + {"name": "TV", "data": [...]}, + {"name": "Music", "data": [...]} + ] + } +``` + + +### get_plays_by_dayofweek +Get graph data by day of the week. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["Sunday", "Monday", "Tuesday", ..., "Saturday"] + "series": + [{"name": "Movies", "data": [...]} + {"name": "TV", "data": [...]}, + {"name": "Music", "data": [...]} + ] + } +``` + + +### get_plays_by_hourofday +Get graph data by hour of the day. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["00", "01", "02", ..., "23"] + "series": + [{"name": "Movies", "data": [...]} + {"name": "TV", "data": [...]}, + {"name": "Music", "data": [...]} + ] + } +``` + + +### get_plays_by_source_resolution +Get graph data by source resolution. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["720", "1080", "sd", ...] + "series": + [{"name": "Direct Play", "data": [...]} + {"name": "Direct Stream", "data": [...]}, + {"name": "Transcode", "data": [...]} + ] + } +``` + + +### get_plays_by_stream_resolution +Get graph data by stream resolution. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["720", "1080", "sd", ...] + "series": + [{"name": "Direct Play", "data": [...]} + {"name": "Direct Stream", "data": [...]}, + {"name": "Transcode", "data": [...]} + ] + } +``` + + +### get_plays_by_stream_type +Get graph data by stream type by date. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["YYYY-MM-DD", "YYYY-MM-DD", ...] + "series": + [{"name": "Direct Play", "data": [...]} + {"name": "Direct Stream", "data": [...]}, + {"name": "Transcode", "data": [...]} + ] + } +``` + + +### get_plays_by_top_10_platforms +Get graph data by top 10 platforms. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["iOS", "Android", "Chrome", ...] + "series": + [{"name": "Movies", "data": [...]} + {"name": "TV", "data": [...]}, + {"name": "Music", "data": [...]} + ] + } +``` + + +### get_plays_by_top_10_users +Get graph data by top 10 users. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["Jon Snow", "DanyKhaleesi69", "A Girl", ...] + "series": + [{"name": "Movies", "data": [...]} + {"name": "TV", "data": [...]}, + {"name": "Music", "data": [...]} + ] + } +``` + + +### get_plays_per_month +Get graph data by month. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["Jan 2016", "Feb 2016", "Mar 2016", ...] + "series": + [{"name": "Movies", "data": [...]} + {"name": "TV", "data": [...]}, + {"name": "Music", "data": [...]} + ] + } +``` + + +### get_plex_log +Get the PMS logs. + +``` +Required parameters: + None + +Optional parameters: + window (int): The number of tail lines to return + log_type (str): "server" or "scanner" + +Returns: + json: + [["May 08, 2016 09:35:37", + "DEBUG", + "Auth: Came in with a super-token, authorization succeeded." + ], + [...], + [...] + ] +``` + + +### get_pms_token +Get the user's Plex token used for PlexPy. + +``` +Required parameters: + username (str): The Plex.tv username + password (str): The Plex.tv password + +Optional parameters: + None + +Returns: + string: The Plex token used for PlexPy +``` + + +### get_recently_added +Get all items that where recelty added to plex. + +``` +Required parameters: + count (str): Number of items to return + +Optional parameters: + section_id (str): The id of the Plex library section + +Returns: + json: + {"recently_added": + [{"added_at": "1461572396", + "grandparent_rating_key": "1219", + "grandparent_thumb": "/library/metadata/1219/thumb/1462175063", + "grandparent_title": "Game of Thrones", + "library_name": "", + "media_index": "1", + "media_type": "episode", + "parent_media_index": "6", + "parent_rating_key": "153036", + "parent_thumb": "/library/metadata/153036/thumb/1462175062", + "parent_title": "", + "rating_key": "153037", + "section_id": "2", + "thumb": "/library/metadata/153037/thumb/1462175060", + "title": "The Red Woman", + "year": "2016" + }, + {...}, + {...} + ] + } +``` + + +### get_server_friendly_name +Get the name of the PMS. + +``` +Required parameters: + None + +Optional parameters: + None + +Returns: + string: "Winterfell-Server" +``` + + +### get_server_id +Get the PMS server identifier. + +``` +Required parameters: + hostname (str): 'localhost' or '192.160.0.10' + port (int): 32400 + +Optional parameters: + ssl (int): 0 or 1 + remote (int): 0 or 1 + +Returns: + string: The unique PMS identifier +``` + + +### get_server_identity +Get info about the local server. + +``` +Required parameters: + None + +Optional parameters: + None + +Returns: + json: + [{"machine_identifier": "ds48g4r354a8v9byrrtr697g3g79w", + "version": "0.9.15.x.xxx-xxxxxxx" + } + ] +``` + + +### get_server_list +Get all your servers that are published to Plex.tv. + +``` +Required parameters: + None + +Optional parameters: + None + +Returns: + json: + [{"clientIdentifier": "ds48g4r354a8v9byrrtr697g3g79w", + "httpsRequired": "0", + "ip": "xxx.xxx.xxx.xxx", + "label": "Winterfell-Server", + "local": "1", + "port": "32400", + "value": "xxx.xxx.xxx.xxx" + }, + {...}, + {...} + ] +``` + + +### get_server_pref +Get a specified PMS server preference. + +``` +Required parameters: + pref (str): Name of preference + +Returns: + string: Value of preference +``` + + +### get_servers_info +Get info about the PMS. + +``` +Required parameters: + None + +Optional parameters: + None + +Returns: + json: + [{"port": "32400", + "host": "10.0.0.97", + "version": "0.9.15.2.1663-7efd046", + "name": "Winterfell-Server", + "machine_identifier": "ds48g4r354a8v9byrrtr697g3g79w" + } + ] +``` + + +### get_settings +Gets all settings from the config file. + +``` +Required parameters: + None + +Optional parameters: + key (str): Name of a config section to return + +Returns: + json: + {"General": {"api_enabled": true, ...} + "Advanced": {"cache_sizemb": "32", ...}, + ... + } +``` + + +### get_stream_type_by_top_10_platforms +Get graph data by stream type by top 10 platforms. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["iOS", "Android", "Chrome", ...] + "series": + [{"name": "Direct Play", "data": [...]} + {"name": "Direct Stream", "data": [...]}, + {"name": "Transcode", "data": [...]} + ] + } +``` + + +### get_stream_type_by_top_10_users +Get graph data by stream type by top 10 users. + +``` +Required parameters: + None + +Optional parameters: + time_range (str): The number of days of data to return + y_axis (str): "plays" or "duration" + user_id (str): The user id to filter the data + +Returns: + json: + {"categories": + ["Jon Snow", "DanyKhaleesi69", "A Girl", ...] + "series": + [{"name": "Direct Play", "data": [...]} + {"name": "Direct Stream", "data": [...]}, + {"name": "Transcode", "data": [...]} + ] + } +``` + + +### get_synced_items +Get a list of synced items on the PMS. + +``` +Required parameters: + machine_id (str): The PMS identifier + +Optional parameters: + user_id (str): The id of the Plex user + +Returns: + json: + [{"content_type": "video", + "device_name": "Tyrion's iPad", + "failure": "", + "friendly_name": "Tyrion Lannister", + "item_complete_count": "0", + "item_count": "1", + "item_downloaded_count": "0", + "item_downloaded_percent_complete": 0, + "metadata_type": "movie", + "music_bitrate": "192", + "photo_quality": "74", + "platform": "iOS", + "rating_key": "154092", + "root_title": "Deadpool", + "state": "pending", + "sync_id": "11617019", + "title": "Deadpool", + "total_size": "0", + "user_id": "696969", + "username": "DrukenDwarfMan", + "video_quality": "60" + }, + {...}, + {...} + ] +``` + + +### get_user_ips +Get the data on PlexPy users IP table. + +``` +Required parameters: + user_id (str): The id of the Plex user + +Optional parameters: + order_column (str): "last_seen", "ip_address", "platform", "player", + "last_played", "play_count" + order_dir (str): "desc" or "asc" + start (int): Row to start from, 0 + length (int): Number of items to return, 25 + search (str): A string to search for, "xxx.xxx.xxx.xxx" + +Returns: + json: + {"draw": 1, + "recordsTotal": 2344, + "recordsFiltered": 10, + "data": + [{"friendly_name": "Jon Snow", + "id": 1121, + "ip_address": "xxx.xxx.xxx.xxx", + "last_played": "Game of Thrones - The Red Woman", + "last_seen": 1462591869, + "media_index": 1, + "media_type": "episode", + "parent_media_index": 6, + "parent_title": "", + "platform": "Chrome", + "play_count": 149, + "player": "Plex Web (Chrome)", + "rating_key": 153037, + "thumb": "/library/metadata/153036/thumb/1462175062", + "transcode_decision": "transcode", + "user_id": 133788, + "year": 2016 + }, + {...}, + {...} + ] + } +``` + + +### get_user_logins +Get the data on PlexPy user login table. + +``` +Required parameters: + user_id (str): The id of the Plex user + +Optional parameters: + order_column (str): "date", "time", "ip_address", "host", "os", "browser" + order_dir (str): "desc" or "asc" + start (int): Row to start from, 0 + length (int): Number of items to return, 25 + search (str): A string to search for, "xxx.xxx.xxx.xxx" + +Returns: + json: + {"draw": 1, + "recordsTotal": 2344, + "recordsFiltered": 10, + "data": + [{"browser": "Safari 7.0.3", + "friendly_name": "Jon Snow", + "host": "http://plexpy.castleblack.com", + "ip_address": "xxx.xxx.xxx.xxx", + "os": "Mac OS X", + "timestamp": 1462591869, + "user": "LordCommanderSnow", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A", + "user_group": "guest", + "user_id": 133788 + }, + {...}, + {...} + ] + } +``` + + +### get_user_names +Get a list of all user and user ids. + +``` +Required parameters: + None + +Optional parameters: + None + +Returns: + json: + [{"friendly_name": "Jon Snow", "user_id": 133788}, + {"friendly_name": "DanyKhaleesi69", "user_id": 8008135}, + {"friendly_name": "Tyrion Lannister", "user_id": 696969}, + {...}, + ] +``` + + +### get_users +Get a list of all users that have access to your server. + +``` +Required parameters: + None + +Optional parameters: + None + +Returns: + json: + [{"email": "Jon.Snow.1337@CastleBlack.com", + "filter_all": "", + "filter_movies": "", + "filter_music": "", + "filter_photos": "", + "filter_tv": "", + "is_allow_sync": null, + "is_home_user": "1", + "is_restricted": "0", + "thumb": "https://plex.tv/users/k10w42309cynaopq/avatar", + "user_id": "133788", + "username": "Jon Snow" + }, + {...}, + {...} + ] +``` + + +### get_users_table +Get the data on PlexPy users table. + +``` +Required parameters: + None + +Optional parameters: + order_column (str): "user_thumb", "friendly_name", "last_seen", "ip_address", "platform", + "player", "last_played", "plays", "duration" + order_dir (str): "desc" or "asc" + start (int): Row to start from, 0 + length (int): Number of items to return, 25 + search (str): A string to search for, "Jon Snow" + +Returns: + json: + {"draw": 1, + "recordsTotal": 10, + "recordsFiltered": 10, + "data": + [{"allow_guest": "Checked", + "do_notify": "Checked", + "duration": 2998290, + "friendly_name": "Jon Snow", + "id": 1121, + "ip_address": "xxx.xxx.xxx.xxx", + "keep_history": "Checked", + "last_played": "Game of Thrones - The Red Woman", + "last_seen": 1462591869, + "media_index": 1, + "media_type": "episode", + "parent_media_index": 6, + "parent_title": "", + "platform": "Chrome", + "player": "Plex Web (Chrome)", + "plays": 487, + "rating_key": 153037, + "thumb": "/library/metadata/153036/thumb/1462175062", + "transcode_decision": "transcode", + "user_id": 133788, + "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar", + "year": 2016 + }, + {...}, + {...} + ] + } +``` + + +### import_database +Import a PlexWatch or Plexivity database into PlexPy. + +``` +Required parameters: + app (str): "plexwatch" or "plexivity" + database_path (str): The full path to the plexwatch database file + table_name (str): "processed" or "grouped" + +Optional parameters: + import_ignore_interval (int): The minimum number of seconds for a stream to import + +Returns: + None +``` + + +### notify +Send a notification using PlexPy. + +``` +Required parameters: + agent_id(str): The id of the notification agent to use + subject(str): The subject of the message + body(str): The body of the message + +Optional parameters: + None + +Returns: + None +``` + + +### refresh_libraries_list +Refresh the PlexPy libraries list. + + +### refresh_users_list +Refresh the PlexPy users list. -### shutdown -No params -Shut down plexpy ### restart -No params -Restart plexpy +Restart PlexPy. + + +### search +Get search results from the PMS. + +``` +Required parameters: + query (str): The query string to search for + +Returns: + json: + {"results_count": 69, + "results_list": + {"movie": + [{...}, + {...}, + ] + }, + {"episode": + [{...}, + {...}, + ] + }, + {...} + } +``` + + +### sql +Query the PlexPy database with raw SQL. Automatically makes a backup of +the database if the latest backup is older then 24h. `api_sql` must be +manually enabled in the config file. + +``` +Required parameters: + query (str): The SQL query + +Optional parameters: + None + +Returns: + None +``` + + +### undelete_library +Restore a deleted library section to PlexPy. + +``` +Required parameters: + section_id (str): The id of the Plex library section + section_name (str): The name of the Plex library section + +Optional parameters: + None + +Returns: + None +``` + + +### undelete_user +Restore a deleted user to PlexPy. + +``` +Required parameters: + user_id (str): The id of the Plex user + username (str): The username of the Plex user + +Optional parameters: + None + +Returns: + None +``` + ### update -No params -Update plexpy - you may want to check the install type in get version and not allow this if type==exe +Check for PlexPy updates on Github. + + +### update_metadata_details +Update the metadata in the PlexPy database by matching rating keys. +Also updates all parents or children of the media item if it is a show/season/episode +or artist/album/track. + +``` +Required parameters: + old_rating_key (str): 12345 + new_rating_key (str): 54321 + media_type (str): "movie", "show", "season", "episode", "artist", "album", "track" + +Optional parameters: + None + +Returns: + None +``` + diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0a6609..95712e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,56 @@ # Changelog +## v1.4.0 (2016-05-15) + +* New: An HTML form login page with sessions support. +* New: Guest access control for shared users using Plex.tv authentication. + * Enable the option in the settings and toggle guest access per user from Users > Edit mode. + * Guests can only view their own user data. Other user info is removed/masked. + * Guests can only view media from libraries that are shared with them (content rating and label filters are respected). Other libraries are removed/masked. + * All settings and admin controls are restricted from guests. + * All current activity on the server is shown, but with masked user/metadata info. +* New: Login logs table on the User and Logs pages. +* New: Filter the history table by user. +* New: Filter the graphs by user. (Thanks @Otger) +* New: Option to hash the admin passowrd in the config file. +* New: Options to enable/disable/rearrange each section on the homepage +* New: Toggle media types for recently added items on the homepage. +* New: Option to enter an Imgur API client ID for uploading posters. + * Note: The shared Imgur client id will be removed in a future PlexPy update! Please enter your own client id in the settings to continue uploading posters! +* New: HTML support for Email. +* New: Posters and HTML support for Telegram. +* New: Poster support for Slack. +* New: Poster support for Twitter. +* New: Re-added Plex Home Theater notification agent. +* New: Browser notification agent (experimental). +* New: Added {plex_url} as a notification option. +* New: Added transcode decision to the activity header. +* New: Documentation for APIv2 (see API.md for details). +* New: Import a Plexivity database into PlexPy. +* New: Prettier fallback image for art/episodes. +* New: Prettier confirm modal dialogues. +* New: Cache images to reduce Plex API calls. This can be disabled in the under Settings > Extra Settings. (Thanks @Hellowlol) +* New: Scheduled backups of the config file. +* New: Button to clear the PlexPy cache/images in the settings. +* New: Button to manually backup the PlexPy database/config in the settings. +* New: Button to clear the PlexPy logs in the settings. +* New: Button to download PlexPy log file on the Logs tab. +* New: Advanced setting in config file to change the Plex API timeout value. +* Fix: Mixed content HTTP request in settings (for reverse proxies with SSL). +* Fix: Rename recently "watched" music to "played". +* Change: Current activity details now persists across refreshes. +* Change: Smoother transitions between preview thumbnails in current activity. +* Change: Datatables now display all columns and scroll horizontally on smaller screens. +* Change: Ability to change the base URL for reverse proxies in the web interface. +* Change: Added a "Verify Server" button in the settings. +* Change: Added request status code in the logs for notifer errors. +* Change: Remove in-memory logs and read lines from log file instead. (Thanks @Hellowlol) +* Change: Limit number of failed attempts to write sessions to history. Default is 5 attempts. +* Change: A bunch of UI updates. +* Change: A bunch of backend code cleanup. +* Removed: All unused Python packages. + + ## v1.3.16 (2016-05-01) * Fix: Viewing photos crashing PlexPy. diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index e297ffb1..9193aa24 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,42 +1,41 @@ -### Reporting Issues: + -**Use proper markdown syntax to structure your post (i.e. code/log in code blocks).** +**Version:** -**Make sure you provide the following information below:** -- [ ] Version +**Branch:** + +**Commit hash:** + +**Operating system:** + +**Python version:** + +**What you did?** + +**What happened?** + +**What you expected?** + +**How can we reproduce your issue?** + + +**What are your (relevant) settings?** + +**Link to logs:** -- [ ] Branch - - -- [ ] Commit hash - - -- [ ] Operating system - - -- [ ] Python version - - -- [ ] What you did? - - -- [ ] What happened? - - -- [ ] What you expected? - - -- [ ] How can we reproduce your issue? - - -- [ ] What are your (relevant) settings? - - -- [ ] Include a link to your **FULL** (not just a few lines!) log file that has the error. Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/). - + diff --git a/PlexPy.py b/PlexPy.py index b601fcee..5ea68130 100755 --- a/PlexPy.py +++ b/PlexPy.py @@ -27,13 +27,14 @@ import sys # Ensure lib added to path, before any other imports sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/')) -from plexpy import webstart, logger, web_socket - -import locale -import time -import signal import argparse +import locale +import signal +import time + import plexpy +from plexpy import config, database, logger, web_socket, webstart + # Register signals, such as CTRL + C signal.signal(signal.SIGINT, plexpy.sig_handler) @@ -147,7 +148,7 @@ def main(): if args.config: config_file = args.config else: - config_file = os.path.join(plexpy.DATA_DIR, 'config.ini') + config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME) # Try to create the DATA_DIR if it doesn't exist if not os.path.exists(plexpy.DATA_DIR): @@ -163,7 +164,7 @@ def main(): 'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...') # Put the database in the DATA_DIR - plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, 'plexpy.db') + plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME) if plexpy.DAEMON: plexpy.daemonize() diff --git a/data/interfaces/default/plexwatch_import.html b/data/interfaces/default/app_import.html similarity index 77% rename from data/interfaces/default/plexwatch_import.html rename to data/interfaces/default/app_import.html index 1330b2b5..63e65c17 100644 --- a/data/interfaces/default/plexwatch_import.html +++ b/data/interfaces/default/app_import.html @@ -2,11 +2,18 @@
@@ -41,7 +48,7 @@
@@ -54,8 +61,13 @@ var table_name = $("#table_name").val(); var import_ignore_interval = $("#import_ignore_interval").val(); $.ajax({ - url: 'get_plexwatch_export_data', - data: {database_path: database_path, table_name:table_name, import_ignore_interval:import_ignore_interval}, + url: 'import_database', + data: { + app: "${app}", + database_path: database_path, + table_name: table_name, + import_ignore_interval: import_ignore_interval + }, cache: false, async: true, success: function(data) { diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index e32ba5d3..771d0ed3 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -1,7 +1,7 @@ <% -import plexpy -from plexpy import version -from plexpy.helpers import anon_url + import plexpy + from plexpy import version + from plexpy.helpers import anon_url %> @@ -12,14 +12,15 @@ from plexpy.helpers import anon_url - - - - + + + + + ${next.headIncludes()} - - + + @@ -33,99 +34,99 @@ from plexpy.helpers import anon_url - + - + - + - + - + - + - + - + - + - - + + - - + + - - + + - - + + - - - - - - - - - - @@ -135,6 +136,7 @@ from plexpy.helpers import anon_url
+ % if _session['user_group'] == 'admin': % if plexpy.CONFIG.CHECK_GITHUB and not plexpy.CURRENT_VERSION: % endif + % endif
+ + +% if _session['user_group'] != 'admin': + +% endif + ${next.headerIncludes()}
${next.body()}
- - - + + + + + +% if _session['user_group'] == 'admin' and plexpy.CONFIG.BROWSER_ENABLED: + +% endif - - ${next.javascriptIncludes()} diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 845dc554..e40415e7 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1,5 +1,5 @@ body { - font-family: 'Open Sans', sans-serif; + font-family: 'Open Sans', Arial, sans-serif; color: #fff; margin-top: 50px; overflow: hidden; @@ -9,7 +9,7 @@ a { } a:hover, a:focus { - color: #f9aa03; + color: #e9a049; text-decoration: none; outline: none; } @@ -89,7 +89,7 @@ img { .nav > li.active > a, .nav > li.active > a:hover, .nav > li.active > a:focus { - color: #f9aa03; + color: #f9be03; background-color: #282828; } .navbar-toggle { @@ -99,6 +99,35 @@ img { .navbar-toggle:focus { background-color: #2f2f2f; } +.nav .open > a, .nav .open > a:hover, .nav .open > a:focus { + background-color: #2f2f2f; + border-color: none; +} +.dropdown-menu { + background-color: #282828; +} +.dropdown-menu .divider { + background-color: #777; +} +.dropdown-menu > li > a { + color: #999; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #fff; + background-color: #2f2f2f; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + background-color: #2f2f2f; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999; +} .icon-bar { background-color: #999; } @@ -109,7 +138,13 @@ img { color: #eee; } .padded-header h3 { - font-size: 20px; + font-size: 16px; + font-weight: bold; + text-transform: uppercase; +} +.padded-header h3 small { + font-size: 13px; + text-transform: none; } .btn { outline:0px !important; @@ -182,9 +217,9 @@ fieldset[disabled] .btn-dark.active { background-color: #3B3B3B; } .btn-bright { - color: #fff; - background-color: #eb8600; - border-color: transparent; + color: #fff; + background-color: #cc7b19; + box-shadow: inset 0 1px 0 #e7993b; } .btn-bright:focus, .btn-bright.focus { @@ -193,14 +228,15 @@ fieldset[disabled] .btn-dark.active { } .btn-bright:hover { color: #fff; - background-color: #E69400; - border-color: #f9aa03; + background-color: #e59029; + box-shadow: inset 0 1px 0 #ebac60; } .btn-bright:active, .btn-bright.active, .open > .dropdown-toggle.btn-bright { color: #fff; - background-color: #eb8600; + background-color: #cc7b19; + box-shadow: inset 0 1px 0 #e7993b; } .btn-bright:active:hover, .btn-bright.active:hover, @@ -212,7 +248,8 @@ fieldset[disabled] .btn-dark.active { .btn-bright.active.focus, .open > .dropdown-toggle.btn-bright.focus { color: #fff; - background-color: #eb8600; + background-color: #cc7b19; + box-shadow: inset 0 1px 0 #e7993b; } .btn-bright:active, .btn-bright.active, @@ -237,19 +274,19 @@ fieldset[disabled] .btn-bright:active, .btn-bright.disabled.active, .btn-bright[disabled].active, fieldset[disabled] .btn-bright.active { - background-color: #c9302c; - border-color: #ac2925; + background-color: #cc7b19; + border-color: #b56d16; } .btn-bright .badge { color: #fff; - background-color: #eb8600; + background-color: #cc7b19; + box-shadow: inset 0 1px 0 #e7993b; } .btn-danger.btn-edit { color: #d7d7d7; background-color: #3B3B3B; border-color: transparent; float: right; - margin-right: 5px; } .btn-danger.btn-edit:hover { color: #fff; @@ -266,11 +303,17 @@ fieldset[disabled] .btn-bright.active { background-color: #ac2925; border-color: #761c19; } +.btn-group select { + margin-top: 0; +} +#user-selection label { + margin-bottom: 0; +} .alert-edit { display: none; - float: right; + float: left; margin-bottom: 0; - margin-right: 5px; + /*margin-right: 5px;*/ padding: 6px 15px; } .modal-header { @@ -377,7 +420,7 @@ textarea.form-control:focus { .pagination > li > span:hover, .pagination > li > a:focus, .pagination > li > span:focus { - background-color: #F9AA03; + background-color: #cc7b19; border: 1px solid #444444; } .pagination > .active > a, @@ -389,7 +432,7 @@ textarea.form-control:focus { z-index: 2; color: #fff; cursor: default; - background-color: #F9AA03; + background-color: #cc7b19; border-color: #444444; } .pagination > .disabled > span, @@ -407,7 +450,7 @@ textarea.form-control:focus { .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { color: #fff; - background-color: #af6c17; + background-color: #cc7b19; } .nav-pills > li > a { border-radius: 3px; @@ -527,6 +570,10 @@ a .users-poster-face:hover { margin-left: 5px; float: left; } +#dashboard-checking-activity, +#dashboard-no-activity { + margin-bottom: 20px; +} .dashboard-instance { float: left; position: relative; @@ -535,17 +582,51 @@ a .users-poster-face:hover { margin-right: 20px; margin-bottom: 20px; } +.dashboard-instance.hover .dashboard-activity-poster { + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #e9a049; + -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #e9a049; + box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #e9a049; +} +.dashboard-instance.hover .dashboard-activity-poster-info-bar { + opacity: 1; +} +.dashboard-instance.hover .dashboard-activity-progress-bar { + height: 14px; + transform-origin: top; + transition: all .2s ease; + border-radius: 0px 0px 3px 3px; +} +.dashboard-instance.hover .bar { + height: 14px; + transform-origin: top; + transition: all .2s ease; + border-radius: 0px 0px 3px 3px; + color: rgba(255, 255, 255, 1); + background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px); + background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px); + background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px); +} +.dashboard-instance.hover .bufferbar { + height: 14px; + transform-origin: top; + transition: all .2s ease; + border-radius: 0px 0px 3px 3px; + color: rgba(255, 255, 255, 1); + background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px); + background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px); + background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px); +} +.dashboard-instance.hover .dashboard-activity-metadata-wrapper { + margin-top: 11px; + transform-origin: top; + transition: all .2s ease; +} .dashboard-activity-poster { height: 200px; width: 100%; position: relative; overflow: hidden; } -a:hover .dashboard-activity-poster { - -webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #e9a049; - -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #e9a049; - box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 2px #e9a049; -} .dashboard-activity-poster-face { background-position: center; background-size: cover; @@ -632,10 +713,10 @@ a:hover .dashboard-activity-poster { } .dashboard-activity-info-details-overlay { text-align: left; - background-image: -webkit-gradient(linear,left 0,left 100%,from(rgba(0,0,0,.6)),to(rgba(0,0,0,.8))); - background-image: -webkit-linear-gradient(top,rgba(0,0,0,.6),0,rgba(0,0,0,.8),100%); - background-image: -moz-linear-gradient(top,rgba(0,0,0,.6) 0,rgba(0,0,0,.8) 100%); - background-image: linear-gradient(to bottom,rgba(0,0,0,.6) 0,rgba(0,0,0,.8) 100%); + background-image: -webkit-gradient(linear,left 0,left 100%,from(rgba(0,0,0,.75)),to(rgba(0,0,0,0))); + background-image: -webkit-linear-gradient(top,rgba(0,0,0,.75),0,rgba(0,0,0,0),100%); + background-image: -moz-linear-gradient(top,rgba(0,0,0,.75) 0,rgba(0,0,0,0) 100%); + background-image: linear-gradient(to bottom,rgba(0,0,0,.75) 0,rgba(0,0,0,0) 100%); background-repeat: repeat-x; position: absolute; top: 0; @@ -703,9 +784,6 @@ a:hover .dashboard-activity-poster { transition: all .2s; z-index: -2; } -.dashboard-activity-poster:hover .dashboard-activity-poster-info-bar { - opacity: 1; -} .dashboard-activity-poster-info-text { position: absolute; bottom: 0; @@ -777,37 +855,6 @@ a:hover .dashboard-activity-poster { height: 6px; overflow: hidden; } -.dashboard-instance.hover .dashboard-activity-progress-bar { - height: 14px; - transform-origin: top; - transition: all .2s ease; - border-radius: 0px 0px 3px 3px; -} -.dashboard-instance.hover .bar { - height: 14px; - transform-origin: top; - transition: all .2s ease; - border-radius: 0px 0px 3px 3px; - color: rgba(255, 255, 255, 1); - background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px); - background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px); - background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px); -} -.dashboard-instance.hover .bufferbar { - height: 14px; - transform-origin: top; - transition: all .2s ease; - border-radius: 0px 0px 3px 3px; - color: rgba(255, 255, 255, 1); - background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px); - background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px); - background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px); -} -.dashboard-instance.hover .dashboard-activity-metadata-wrapper { - margin-top: 11px; - transform-origin: top; - transition: all .2s ease; -} .dashboard-activity-metadata-wrapper { position: relative; width: 100%; @@ -819,7 +866,8 @@ a:hover .dashboard-activity-poster { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - font-size: 14px; + font-size: 13px; + font-weight: bold; line-height: 25px; color: #fff; max-width: 300px; @@ -828,7 +876,8 @@ a:hover .dashboard-activity-poster { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - font-size: 14px; + font-size: 13px; + font-weight: bold; line-height: 25px; color: #999; max-width: 172px; @@ -838,7 +887,8 @@ a:hover .dashboard-activity-poster { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - font-size: 14px; + font-size: 13px; + font-weight: bold; line-height: 25px; color: #999; text-align: right; @@ -846,6 +896,7 @@ a:hover .dashboard-activity-poster { float: right; } .dashboard-activity-metadata-user-thumb { + background-color: #282828; background-position: center; background-size: cover; margin-top: 5px; @@ -871,16 +922,16 @@ a .dashboard-activity-metadata-user-thumb:hover { color: #999; } .dashboard-activity-metadata-user a:hover { - color: #F9AA03; + color: #e9a049; } .dashboard-activity-metadata-title a:hover { - color: #F9AA03; + color: #e9a049; } .dashboard-activity-metadata-progress-wrapper { margin-bottom: 20px; font-size: 12px; font-weight: bold; - color: #F9AA03; + color: #e9a049; } .dashboard-recent-media-row { width: 100%; @@ -957,6 +1008,7 @@ a:hover .dashboard-recent-media-cover { .dashboard-recent-media-overlay-text { color: #aaa; font-size: 12px; + font-weight: bold; float: left; position: absolute; left: 8px; @@ -974,9 +1026,9 @@ a:hover .dashboard-recent-media-cover { overflow: hidden; position: relative; font-size: 13px; + font-weight: bold; margin: 0; - line-height: 15px; - font-weight: normal; + line-height: 16px; width: 150px; white-space: nowrap; text-align: left; @@ -985,13 +1037,11 @@ a:hover .dashboard-recent-media-cover { .dashboard-recent-media-metacontainer h3.text-muted { color: #777; } -.dashboard-recent-media-metacontainer .text-muted { - padding: 5px 3px 0 3px; - text-overflow: ellipsis; - overflow: hidden; - position: relative; - white-space: nowrap; - text-align: left; +.dashboard-recent-media-metacontainer h3.text-muted a { + color: #777; +} +.dashboard-recent-media-metacontainer h3.text-muted a:hover { + color: #e9a049; } .art-face { background-repeat: no-repeat; @@ -999,7 +1049,7 @@ a:hover .dashboard-recent-media-cover { background-attachment: scroll; background-size: cover; opacity: 0; - position: absolute; + position: fixed; top: 0; bottom: 0; width: 100%; @@ -1021,10 +1071,11 @@ a:hover .dashboard-recent-media-cover { left: 0; overflow-x: hidden; overflow-y: auto; + -webkit-overflow-scrolling: touch; } .summary-container .table-card-header, .summary-container .table-card-back { - opacity: 0.90; + background: rgba(40,40,40, 0.9); } .summary-navbar { background-color: rgba(255,255,255,.03); @@ -1057,7 +1108,7 @@ a:hover .dashboard-recent-media-cover { color: #999; } .summary-navbar-list .breadcrumb a:hover { - color: #F9AA03; + color: #f9be03; } .summary-content-title-wrapper { height: 150px; @@ -1071,7 +1122,7 @@ a:hover .dashboard-recent-media-cover { .summary-content-title h1 { margin-top: 0; margin-bottom: 10px; - color: #F9AA03; + color: #f9be03; font-size: 28px; line-height: 40px; float: left; @@ -1082,11 +1133,10 @@ a:hover .dashboard-recent-media-cover { width: 100%; } .summary-content-title h1 a { - color: #F9AA03; + color: #f9be03; } .summary-content-title h1 a:hover { - color: #F9AA03; - text-decoration: underline; + color: #fff; } .summary-content-title h2 { margin-top: 0; @@ -1118,7 +1168,11 @@ a:hover .dashboard-recent-media-cover { color: #999; } .summary-content-wrapper { - background-image: linear-gradient(rgba(0,0,0,.4),rgba(19,19,19,.4) 50%,rgba(26,26,26,.4)); + background: rgba(0,0,0,.4); + background: -webkit-linear-gradient(top, rgba(0,0,0,.4), rgba(10,10,10,.4)); + background: -o-linear-gradient(bottom, rgba(0,0,0,.4), rgba(10,10,10,.4)); + background: -moz-linear-gradient(bottom, rgba(0,0,0,.4), rgba(10,10,10,.4)); + background: linear-gradient(to bottom, rgba(0,0,0,.4), rgba(10,10,10,.4)); background-clip: content-box; min-height: calc(100% - 200px); position: inherit; @@ -1335,7 +1389,7 @@ a:hover .summary-poster-face-track .summary-poster-face-overlay span { .star-rating .star-icon { width: auto; margin-left: 2px; - color: #F9AA03; + color: #f9be03; } .star-rating .star-icon-o { width: auto; @@ -1438,6 +1492,7 @@ a:hover .item-children-poster { .item-children-overlay-text { color: #aaa; font-size: 12px; + font-weight: bold; float: left; position: absolute; left: 8px; @@ -1465,9 +1520,9 @@ a:hover .item-children-poster { overflow: hidden; position: relative; font-size: 13px; + font-weight: bold; margin: 0; - line-height: 15px; - font-weight: normal; + line-height: 16px; white-space: nowrap; text-align: left; clear: both; @@ -1475,6 +1530,12 @@ a:hover .item-children-poster { .item-children-instance-text-wrapper h3.text-muted { color: #777; } +.item-children-instance-text-wrapper h3.text-muted a { + color: #777; +} +.item-children-instance-text-wrapper h3.text-muted a:hover { + color: #e9a049; +} .item-children-list-item-odd { border-top: 0px solid #343434; border-bottom: 0px solid #343434; @@ -1519,7 +1580,7 @@ a:hover .item-children-poster { margin-right: 20px; } #new_title h3 { - color: #F9AA03; + color: #f9be03; font-size: 14px; line-height: 1.42857143; font-weight: bold; @@ -1554,12 +1615,10 @@ a:hover .item-children-poster { left: 12px; } .user-info-wrapper { - height: 113px; } .user-info-poster-face { float: left; - margin-top: 15px; - margin-right: 15px; + margin: 15px 15px 15px 0; background-size: cover; background-position: center; height: 80px; @@ -1574,22 +1633,26 @@ a:hover .item-children-poster { .user-info-username { font-size: 24px; color: #fff; - position: relative; - top: 27px; + padding-top: 27px; + padding-left: 110px; } .user-info-nav { - position: relative; - top: 15px; - left: -5px; + margin-top: 15px; } -.user-info-nav > .active > a, .nav-tabs > .active > a:hover, .nav-tabs > .active > a:focus { - color: #F9AA03; +.user-info-nav > .active > a { + color: #cc7b19; +} +.nav-tabs > .active > a:hover, +.nav-tabs > .active > a:focus { + color: #e9a049; } .user-info-nav a:hover { + color: #e9a049; text-decoration: none; } .user-info-nav ul { list-style: none; + padding: 0; } .user-info-nav li { float: left; @@ -1640,7 +1703,7 @@ a:hover .item-children-poster { .user-overview-stats-instance h3 { font-size: 30px; font-weight: bold; - color: #F9AA03; + color: #f9be03; line-height: 22px; position: relative; top: 5px; @@ -1692,8 +1755,8 @@ a:hover .item-children-poster { text-overflow: ellipsis; overflow: hidden; position: relative; - font-size: 13px; - line-height: 15px; + font-size: 14px; + line-height: 16px; font-weight: normal; width: 140px; margin-left: 10px; @@ -1702,7 +1765,7 @@ a:hover .item-children-poster { .user-player-instance-playcount h3 { font-size: 30px; font-weight: bold; - color: #F9AA03; + color: #f9be03; line-height: 22px; position: relative; top: 5px; @@ -1719,8 +1782,7 @@ a:hover .item-children-poster { } .library-info-poster-face { float: left; - margin-top: 15px; - margin-right: 15px; + margin: 15px 15px 15px 0; background-size: cover; background-position: center; height: 80px; @@ -1742,7 +1804,7 @@ a:hover .item-children-poster { height: 80px; width: 80px; } -.library-user-instance-box:hover { +a .library-user-instance-box: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; @@ -1784,8 +1846,8 @@ a:hover .item-children-poster { text-overflow: ellipsis; overflow: hidden; position: relative; - font-size: 13px; - line-height: 15px; + font-size: 14px; + line-height: 16px; font-weight: bold; width: 100%; padding: 0 0 0 20px; @@ -1810,7 +1872,7 @@ a:hover .item-children-poster { .home-platforms-instance-playcount h3 { font-size: 30px; font-weight: bold; - color: #F9AA03; + color: #f9be03; line-height: 22px; position: relative; top: 5px; @@ -1919,7 +1981,7 @@ a:hover .item-children-poster { height: 60px; } .home-platforms-instance-list-number { - background-color: #eb8600; + background-color: #f9be03; float: left; position: absolute; top: -10px; @@ -1976,7 +2038,7 @@ a:hover .item-children-poster { .home-platforms-instance-list-playcount h3 { font-size: 20px; font-weight: bold; - color: #F9AA03; + color: #f9be03; line-height: 22px; position: relative; margin: 0 5px 0 0; @@ -2071,10 +2133,10 @@ a:hover .item-children-poster { transition: all 0.3s ease; } .home-platforms-instance-list-chevron i:hover { - color: #eb8600; + color: #f9be03; } .home-platforms-instance-list-chevron.active i.fa-chevron-down{ - color: #eb8600; + color: #f9be03; -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); @@ -2084,8 +2146,8 @@ a .home-platforms-instance-box:hover, a .home-platforms-instance-oval:hover, a .home-platforms-instance-list-box:hover, a .home-platforms-instance-list-oval:hover, -.home-platforms-poster-face:hover, -.home-platforms-list-poster-face:hover +a .home-platforms-poster-face:hover, +a .home-platforms-list-poster-face:hover { -webkit-box-shadow: inset 0 0 0 2px #e9a049; -moz-box-shadow: inset 0 0 0 2px #e9a049; @@ -2173,6 +2235,7 @@ a .home-platforms-instance-list-oval:hover, } .header-bar span { font-size: 22px; + font-weight: bold; line-height: 34px; } .button-bar { @@ -2181,11 +2244,11 @@ a .home-platforms-instance-list-oval:hover, .colvis-button-bar, .refresh-users-button, .refresh-libraries-button { - float: right; + /*float: right;*/ } .refresh-users-button, .refresh-libraries-button { - margin-right: 5px; + /*margin-right: 5px;*/ } .nav-settings, .nav-settings ul { @@ -2200,6 +2263,7 @@ a .home-platforms-instance-list-oval:hover, border-top: 1px solid #2d2d2d; } .nav-settings > li > a { + border-bottom: 1px solid #232323; display: block; padding: 15px 15px 15px 15px; color: #999; @@ -2215,7 +2279,7 @@ a .home-platforms-instance-list-oval:hover, .nav-settings > .active > a, .nav-settings > .active > a:hover, .nav-settings > .active > a:focus { - color: #eb8600; + color: #f9be03; background-color: #2f2f2f; } .stacked-configs, @@ -2257,7 +2321,7 @@ a .home-platforms-instance-list-oval:hover, color: #eee; } .stacked-configs > li > span > a.active { - color: #eb8600; + color: #f9be03; } .accordion { width: 100%; @@ -2299,10 +2363,10 @@ a .home-platforms-instance-list-oval:hover, background: #2f2f2f; } .accordion li.open .link { - color: #eb8600; + color: #f9be03; } .accordion li.open i { - color: #eb8600; + color: #f9be03; } .accordion li.open i.fa-chevron-down { -webkit-transform: rotate(180deg); @@ -2330,7 +2394,7 @@ a .home-platforms-instance-list-oval:hover, transition: all 0.25s ease; } .submenu a:hover { - background: #eb8600; + background: #f9be03; color: #FFF; } .ajaxMsg { @@ -2532,7 +2596,7 @@ a .home-platforms-instance-list-oval:hover, margin-right: 3px; } #updatebar a:hover { - color: #F9AA03; + color: #e9a049; } .body-container { position: absolute; @@ -2593,13 +2657,18 @@ table.display tr.shown + tr .pagination > .active > a:hover { table.display tr.shown + tr table[id^='history_child'] td:hover a, table.display tr.shown + tr table[id^='media_info_child'] > tr > td:hover a, table.display tr.shown + tr table[id^='media_info_child'] tr.shown + tr table[id^='media_info_child'] td:hover a { - color: #F9AA03; + color: #cc7b19; } -table.display tr.shown + tr .pagination > .disabled > a { +table.display tr.shown + tr .pagination > .disabled > a, +table.display tr.shown + tr .pagination > .disabled > a:hover { color: #444444; } table.display tr.shown + tr .pagination > li > a:hover { - color: #23527c; + color: #e9a049; +} +table.display tr.odd td, +table.display tr.even td { + padding: 5px 10px !important; } table[id^='history_child'] { margin-top: 0; @@ -2608,17 +2677,32 @@ table[id^='history_child'] { table[id^='media_info_child'] { margin-top: 0; } -table[id^='history_child'] thead th, -table[id^='media_info_child'] thead th { +div[id^='history_child'] thead th, +div[id^='media_info_child'] thead th { line-height: 0; height: 0 !important; overflow: hidden; } -table[id^='media_info_child'] table[id^='media_info_child'] thead th { +div[id^='history_child'] div.row, +div[id^='media_info_child'] div.row { + margin: 0; +} +div[id^='history_child'] div.col-sm-12, +div[id^='media_info_child'] div.col-sm-12 { + padding: 0; +} +div[id^='history_child'] div.dataTables_scrollBody, +div[id^='media_info_child'] div.dataTables_scrollBody { + overflow: hidden !important; +} +div[id^='media_info_child'] div[id^='media_info_child'] div.dataTables_scrollHead thead th { line-height: 25px; height: 35px !important; overflow: hidden; } +.dataTables_scrollBody { + -webkit-overflow-scrolling: touch; +} #search_form { width: 300px; padding: 8px 15px; @@ -2791,4 +2875,83 @@ a.no-highlight:hover { #recently-added-row-scroller, #recently-watched-row-scroller { position: relative; + height: 265px; + margin-bottom: 25px; +} + +@media (min-width: 768px) { + .login-container { + max-width: 750px; + } +} +@media (min-width: 992px) { + .login-container { + max-width: 970px; + } +} +@media (min-width: 1200px) { + .login-container { + max-width: 1170px; + } +} +.login-container { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +.login { + margin: 0 auto; +} +.login-logo { + margin: 0 auto 50px auto; + width: 340px; + height: 100px; +} +.login-container .form-group { + margin-bottom: 20px; +} +.login-container .form-group label { + font-weight: 400; + color: #999; +} +.login-container .form-control { + height: 38px; + line-height: 1.5em; +} +.login-container .form-footer { + margin-top: 40px; +} +.login-container .login-button { + float: right; + text-transform: uppercase; + text-shadow: 0 -1px 1px rgba(0,0,0,.4),0 0 15px rgba(0,0,0,.2); +} +.login-container .remember-group { + float: left; + color: #999; +} +.login-container .remember-group .control-label { + display: inline; + margin-bottom: 0; + font-weight: 400; + cursor: pointer; +} +#admin-login-modal .form-group label { + font-weight: 400; + color: #999; +} +#admin-login-modal .remember-group { + float: left; + color: #999; +} +#admin-login-modal .remember-group .control-label { + display: inline; + margin-bottom: 0; + font-weight: 400; + cursor: pointer; +} +.datatable-wrap { + min-width: 150px; + max-width: 250px; } \ No newline at end of file diff --git a/data/interfaces/default/css/pnotify.custom.min.css b/data/interfaces/default/css/pnotify.custom.min.css new file mode 100644 index 00000000..2d6056a2 --- /dev/null +++ b/data/interfaces/default/css/pnotify.custom.min.css @@ -0,0 +1 @@ +.ui-pnotify{top:36px;right:36px;position:absolute;height:auto;z-index:2}body>.ui-pnotify{position:fixed;z-index:100040}.ui-pnotify-modal-overlay{background-color:rgba(0,0,0,.4);top:0;left:0;position:absolute;height:100%;width:100%;z-index:1}body>.ui-pnotify-modal-overlay{position:fixed;z-index:100039}.ui-pnotify.ui-pnotify-in{display:block!important}.ui-pnotify.ui-pnotify-move{transition:left .5s ease,top .5s ease,right .5s ease,bottom .5s ease}.ui-pnotify.ui-pnotify-fade-slow{transition:opacity .6s linear;opacity:0}.ui-pnotify.ui-pnotify-fade-slow.ui-pnotify.ui-pnotify-move{transition:opacity .6s linear,left .5s ease,top .5s ease,right .5s ease,bottom .5s ease}.ui-pnotify.ui-pnotify-fade-normal{transition:opacity .4s linear;opacity:0}.ui-pnotify.ui-pnotify-fade-normal.ui-pnotify.ui-pnotify-move{transition:opacity .4s linear,left .5s ease,top .5s ease,right .5s ease,bottom .5s ease}.ui-pnotify.ui-pnotify-fade-fast{transition:opacity .2s linear;opacity:0}.ui-pnotify.ui-pnotify-fade-fast.ui-pnotify.ui-pnotify-move{transition:opacity .2s linear,left .5s ease,top .5s ease,right .5s ease,bottom .5s ease}.ui-pnotify.ui-pnotify-fade-in{opacity:1}.ui-pnotify .ui-pnotify-shadow{-webkit-box-shadow:0 6px 28px 0 rgba(0,0,0,.1);-moz-box-shadow:0 6px 28px 0 rgba(0,0,0,.1);box-shadow:0 6px 28px 0 rgba(0,0,0,.1)}.ui-pnotify-container{background-position:0 0;padding:.8em;height:100%;margin:0}.ui-pnotify-container:after{content:" ";visibility:hidden;display:block;height:0;clear:both}.ui-pnotify-container.ui-pnotify-sharp{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.ui-pnotify-title{display:block;margin-bottom:.4em;margin-top:0}.ui-pnotify-text{display:block}.ui-pnotify-icon,.ui-pnotify-icon span{display:block;float:left;margin-right:.2em}.ui-pnotify.stack-bottomleft,.ui-pnotify.stack-topleft{left:25px;right:auto}.ui-pnotify.stack-bottomleft,.ui-pnotify.stack-bottomright{bottom:25px;top:auto}.ui-pnotify.stack-modal{left:50%;right:auto;margin-left:-150px}.ui-pnotify-closer,.ui-pnotify-sticker{float:right;margin-left:.2em}.ui-pnotify-container{position:relative;left:0}@media (max-width:480px){.ui-pnotify-mobile-able.ui-pnotify{position:fixed;top:0;right:0;left:0;width:auto!important;font-size:1.2em;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;-ms-font-smoothing:antialiased;font-smoothing:antialiased}.ui-pnotify-mobile-able.ui-pnotify .ui-pnotify-shadow{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-bottom-width:5px}.ui-pnotify-mobile-able .ui-pnotify-container{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.ui-pnotify-mobile-able.ui-pnotify.stack-bottomleft,.ui-pnotify-mobile-able.ui-pnotify.stack-topleft{left:0;right:0}.ui-pnotify-mobile-able.ui-pnotify.stack-bottomleft,.ui-pnotify-mobile-able.ui-pnotify.stack-bottomright{left:0;right:0;bottom:0;top:auto}.ui-pnotify-mobile-able.ui-pnotify.stack-bottomleft .ui-pnotify-shadow,.ui-pnotify-mobile-able.ui-pnotify.stack-bottomright .ui-pnotify-shadow{border-top-width:5px;border-bottom-width:1px}} \ No newline at end of file diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 4d93b7f7..675904a5 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -68,20 +68,23 @@ DOCUMENTATION :: END % if data['stream_count'] != '0': % for a in data['sessions']:
- % if a['media_type'] == 'movie' or a['media_type'] == 'episode' or a['media_type'] == 'track': + % if (a['media_type'] == 'movie' or a['media_type'] == 'episode' or a['media_type'] == 'track') and a['rating_key']: + % else: + % endif
+ % if not a['art'].startswith('interfaces') or not a['thumb'].startswith('interfaces'): % if (a['media_type'] == 'movie' and not a['indexes']) or (a['indexes'] and not a['view_offset']): -
+
% elif (a['media_type'] == 'episode' and not a['indexes']) or (a['indexes'] and not a['view_offset']): -
+
% elif a['indexes']: - + % else: % if a['media_type'] == 'track': -
-
+
+
% elif a['media_type'] == 'clip': % if a['art'][:4] == 'http':
@@ -89,17 +92,20 @@ DOCUMENTATION :: END
% else: % if a['art']: -
+
% else: -
+
% endif % endif % elif a['media_type'] == 'photo': -
+
% else:
% endif % endif + % else: +
+ % endif
% endif
- % if a['media_type'] == 'movie' or a['media_type'] == 'episode' or a['media_type'] == 'track': + % if (a['media_type'] == 'movie' or a['media_type'] == 'episode' or a['media_type'] == 'track') and a['rating_key']: +
+ % else: % endif
@@ -222,9 +230,13 @@ DOCUMENTATION :: END
+ % if a['user_id']: + % else: +
+ % endif
% if a['state'] == 'playing':   @@ -233,6 +245,7 @@ DOCUMENTATION :: END % elif a['state'] == 'buffering':   % endif + % if a['rating_key']: % if a['media_type'] == 'episode': ${a['grandparent_title']} - ${a['title']} % elif a['media_type'] == 'movie': @@ -246,8 +259,12 @@ DOCUMENTATION :: END % else: ${a['title']} % endif + % else: + ${a['title']} + % endif
+ % if a['rating_key']: % if a['media_type'] == 'episode': S${a['parent_media_index']} · E${a['media_index']} % elif a['media_type'] == 'movie': @@ -259,9 +276,14 @@ DOCUMENTATION :: END % else: ${a['year']} % endif + % endif
+ % if a['user_id']: ${a['friendly_name']} + % else: + ${a['friendly_name']} + % endif
diff --git a/data/interfaces/default/current_activity_header.html b/data/interfaces/default/current_activity_header.html index 04d8f65b..042450b1 100644 --- a/data/interfaces/default/current_activity_header.html +++ b/data/interfaces/default/current_activity_header.html @@ -15,12 +15,23 @@ DOCUMENTATION :: END % if data != None: - % if data == '0': +<% + s = '(' + if data['direct_play']: + s += str(data['direct_play']) + ' direct play' + ('s' if data['direct_play'] > 1 else '') + ', ' + if data['direct_stream']: + s += str(data['direct_stream']) + ' direct stream' + ('s' if data['direct_stream'] > 1 else '') + ', ' + if data['transcode']: + s += str(data['transcode']) + ' transcode' + ('s' if data['transcode'] > 1 else '') + ', ' + s = s.rstrip(', ') + s += ')' +%> + % if data['stream_count'] == '0':

Activity

- % elif data == '1': -

Activity ${data} stream

+ % elif data['stream_count'] == '1': +

Activity   ${data['stream_count']} stream ${s}

% else: -

Activity ${data} streams

+

Activity   ${data['stream_count']} streams ${s}

% endif % else:

Activity

diff --git a/data/interfaces/default/current_activity_instance.html b/data/interfaces/default/current_activity_instance.html new file mode 100644 index 00000000..5a51b99b --- /dev/null +++ b/data/interfaces/default/current_activity_instance.html @@ -0,0 +1,311 @@ +<%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: current_activity_instance.html +Version: 0.1 +Variable names: data {dict} + +data :: Usable parameters + +== Global keys == +session_key Returns a unique session id for the active stream +rating_key Returns the unique identifier for the media item. +media_index Returns the index of the media item. +parent_media_index Returns the index of the media item's parent. +media_type Returns the type of session. Either 'track', 'episode' or 'movie'. +thumb Returns the location of the item's thumbnail. Use with pms_image_proxy. +bif_thumb Returns the location of the item's bif thumbnail. Use with pms_image_proxy. +art Returns the location of the item's artwork +progress_percent Returns the current progress of the item. 0 to 100. +user Returns the name of the user owning the session. +user_id Returns the Plex user id if available. +machine_id Returns the machine id of the players being used. +friendly_name Returns the friendlly name of the user owning the session. +user_thumb Returns the profile picture of the user owning the session. +state Returns the state of the current session. Either 'playing', 'paused' or 'buffering'. +title Returns the name of the episode, movie or music track. +year Returns the year of the episode, movie, or clip. +ip_address Returns the ip address of the stream. +player Returns the name of the platform used to play the stream. +platform Returns the type of platform used to play the stream. +throttled Returns true if the transcode session is throttled. +transcode_progress Returns the current transcode progress of the item. 0 to 100. +transcode_speed Returns the current transcode speed of the item. +audio_decision Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'. +audio_codec Returns the name of the audio codec. +audio_channels Returns the number of audio channels. +grandparent_title Returns the title of the item's grandparent. +parent_title Returns the title of the item's parent. +video_decision Returns the video transcode decision. Either 'transcode', 'copy' or 'direct play'. +video_codec Returns the name of the video codec. +height Returns the value of the video height. +width Returns the value of the video width. +container Returns the value of the media container. +bitrate Returns the value of the media bitrate. +video_resolution Returns the value of the video resolution. +video_framerate Returns the value of the video framerate. +video_aspect_ratio Returns the value of the video aspect ratio. +transcode_audio_channels Returns the amount of audio channels if there is a transcode session. +transcode_audio_codec Returns the name of the audio codec if there is a transcode session. +transcode_video_codec Returns the name of the video codec if there is a transcode session. +transcode_width Returns the video width if there is a transcode session. +transcode_height Returns the video height if there is a transcode session. +transcode_container Returns the value of media container if there is a transcode session. +transcode_protocol Returns the value of media protocol if there is a transcode session. +indexes Returns true if the media has media indexes and are enabled + +DOCUMENTATION :: END + + +% if data is not None: +<% + from plexpy import helpers + data['indexes'] = helpers.cast_to_int(data['indexes']) +%> +
+
+ % if (data['media_type'] == 'movie' or data['media_type'] == 'episode' or data['media_type'] == 'track') and data['rating_key']: + + % else: + + % endif +
+ % if not data['art'].startswith('interfaces') or not data['thumb'].startswith('interfaces'): + % if (data['media_type'] == 'movie' and not data['indexes']) or (data['indexes'] and not data['view_offset']): +
+ % elif (data['media_type'] == 'episode' and not data['indexes']) or (data['indexes'] and not data['view_offset']): +
+ % elif data['indexes']: + + % else: + % if data['media_type'] == 'track': +
+
+ % elif data['media_type'] == 'clip': + % if data['art'].startswith('http'): +
+ % elif data['thumb'].startswith('http'): +
+ % else: + % if data['art']: +
+ % else: +
+ % endif + % endif + % elif data['media_type'] == 'photo': +
+ % else: +
+ % endif + % endif + % else: +
+ % endif +
+ +
+
+
+
+ +
+
+ ${data['player']}
+ + % if data['state'] == 'playing': + State  Playing + % elif data['state'] == 'paused': + State  Paused + % elif data['state'] == 'buffering': + State  Buffering + % endif + +
+ % if data['media_type'] == 'track': + % if data['audio_decision'] == 'direct play': + Stream  Direct Play + % elif data['audio_decision'] == 'copy': + Stream  Direct Stream + % else: + Stream   + Transcoding + + (Speed: ${data['transcode_speed']}) + % if data['throttled'] == '1': + (Throttled) + % endif + + + % endif +
+ % if data['audio_decision'] == 'direct play': + Audio  Direct Play (${data['audio_codec']}) (${data['audio_channels']}ch) + % elif data['audio_decision'] == 'copy': + Audio  Direct Stream (${data['transcode_audio_codec']}) (${data['transcode_audio_channels']}ch) + % elif data['audio_decision'] == 'transcode': + Audio  Transcode (${data['transcode_audio_codec']}) (${data['transcode_audio_channels']}ch) + % endif + % elif data['media_type'] == 'episode' or data['media_type'] == 'movie' or data['media_type'] == 'clip': + % if data['video_decision'] == 'direct play' and data['audio_decision'] == 'direct play': + Stream  Direct Play + % elif data['video_decision'] == 'copy' and data['audio_decision'] == 'copy': + Stream  Direct Stream + % else: + Stream   + Transcoding + + (Speed: ${data['transcode_speed']}) + % if data['throttled'] == '1': + (Throttled) + % endif + + + % endif +
+ % if data['video_decision'] == 'direct play': + Video  Direct Play (${data['video_codec']}) (${data['width']}x${data['height']}) + % elif data['video_decision'] == 'copy': + Video  Direct Stream (${data['transcode_video_codec']}) (${data['width']}x${data['height']}) + % elif data['video_decision'] == 'transcode': + Video  Transcode (${data['transcode_video_codec']}) (${data['transcode_width']}x${data['transcode_height']}) + % endif +
+ % if data['audio_decision'] == 'direct play': + Audio  Direct Play (${data['audio_codec']}) (${data['audio_channels']}ch) + % elif data['audio_decision'] == 'copy': + Audio  Direct Stream (${data['transcode_audio_codec']}) (${data['transcode_audio_channels']}ch) + % elif data['audio_decision'] == 'transcode': + Audio  Transcode (${data['transcode_audio_codec']}) (${data['transcode_audio_channels']}ch) + % endif + % elif data['media_type'] == 'photo': + % if data['video_decision'] == 'direct play': + Stream  Direct Play + % elif data['video_decision'] == 'copy': + Stream  Direct Stream + % else: + Stream   + + (Speed: ${data['transcode_speed']}) + % if data['throttled'] == '1': + (Throttled) + % endif + + + % endif + % endif +
+
+
+ % if data['media_type'] != 'photo': +
+
+ % if data['ip_address']: + IP: ${data['ip_address']} + % else: + IP: N/A + % endif +
+ ETA: + + + +
+
+ + + / + + +
+
+ % endif +
+ % if (data['media_type'] == 'movie' or data['media_type'] == 'episode' or data['media_type'] == 'track') and data['rating_key']: +
+ % else: + + % endif +
+
+
${data['transcode_progress']}%
+
${data['progress_percent']}%
+
+
+
+
+ % if data['user_id']: + + + + % else: + + % endif + + + +
+
+% endif \ No newline at end of file diff --git a/data/interfaces/default/edit_user.html b/data/interfaces/default/edit_user.html index 8d5626ca..d912a788 100644 --- a/data/interfaces/default/edit_user.html +++ b/data/interfaces/default/edit_user.html @@ -20,6 +20,7 @@ is_allow_sync Returns bool value for whether the user has sync rights. is_restricted Returns bool value for whether the user account is restricted. do_notify Returns bool value for whether to send notifications for the user. keep_history Returns bool value for whether to keep history for the user. +allow_guest Returns bool value for whether to allow guest access for the user. DOCUMENTATION :: END @@ -67,6 +68,12 @@ DOCUMENTATION :: END

Uncheck this if you do not want to keep any history on this user's activity.

+
+ +

Uncheck this if you do not want to allow this user to login to PlexPy.

+
% if data['user_id']:
@@ -83,7 +90,7 @@ DOCUMENTATION :: END
-
-
+
% if len(top_stat['rows']) > 1:
@@ -839,7 +979,7 @@ DOCUMENTATION :: END
-
+
% endif diff --git a/data/interfaces/default/images/art.png b/data/interfaces/default/images/art.png new file mode 100644 index 00000000..997b001f Binary files /dev/null and b/data/interfaces/default/images/art.png differ diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index a430bfcc..36d2887e 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -5,22 +5,23 @@ <%def name="body()">
+ % for section in config['home_sections']: + % if section == 'current_activity':

Activity

-
Checking for activity...
-
+
Checking for activity...
- % if config['home_stats_cards']: + % elif section == 'watch_stats':
-

Watch Statistics Last ${config['home_stats_length']} days

+

Watch Statistics   Last ${config['home_stats_length']} days

Loading stats...
@@ -28,12 +29,11 @@
- % endif - % if config['home_library_cards']: + % elif section == 'library_stats':
-

Library Statistics ${config['pms_name']}

+

Library Statistics   ${config['pms_name']}

Loading stats...
@@ -41,11 +41,11 @@
- % endif + % elif section == 'recently_added':
-
Looking for new items...
@@ -61,96 +64,272 @@
+ % endif + % endfor
<%def name="javascriptIncludes()"> - + + +% if 'current_activity' in config['home_sections']: +% endif +% if 'watch_stats' in config['home_sections']: + +% endif +% if 'library_stats' in config['home_sections']: + +% endif +% if 'recently_added' in config['home_sections']: + - - +% endif + \ No newline at end of file diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 3186595d..4a0cc9aa 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -36,9 +36,10 @@ DOCUMENTATION :: END <%! - from plexpy import common import re + from plexpy import common + # Get audio codec file def af(codec): for pattern, file in common.MEDIA_FLAGS_AUDIO.iteritems(): @@ -57,9 +58,9 @@ DOCUMENTATION :: END <%inherit file="base.html"/> <%def name="headIncludes()"> - - - + + + <%def name="body()"> @@ -113,13 +114,13 @@ DOCUMENTATION :: END % endif % if data['media_type'] == 'episode': -
+
% elif data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track': -
+
@@ -172,16 +173,16 @@ DOCUMENTATION :: END % if data['media_type'] == 'movie' or data['media_type'] == 'episode' or data['media_type'] == 'track':
% if data['media_type'] != 'track' and data['video_codec']: - + % endif % if data['media_type'] != 'track' and data['video_resolution']: - + % endif % if data['audio_codec']: - + % endif % if data['audio_channels']: - + % endif
% endif @@ -290,7 +291,7 @@ DOCUMENTATION :: END
% if data['media_type'] == 'show': -
+
Season List for ${data['title']} @@ -301,7 +302,7 @@ DOCUMENTATION :: END
% elif data['media_type'] == 'season': -
+
Episode List for ${data['title']} @@ -312,7 +313,7 @@ DOCUMENTATION :: END
% elif data['media_type'] == 'artist': -
+
Album List for ${data['title']} @@ -323,7 +324,7 @@ DOCUMENTATION :: END
% elif data['media_type'] == 'album': -
+
Track List for ${data['title']} @@ -334,33 +335,41 @@ DOCUMENTATION :: END
% endif -
+
Watch History for ${data['title']}
- - + % if _session['user_group'] == 'admin': + +
+   +
% if source == 'history': - - Fix Metadata - +
% endif % if data.get('poster_url'): - % if data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track': - - % else: - +
+ % if data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track': + + % else: + + % endif + + +
% endif - -
% endif - +
@@ -388,7 +397,7 @@ DOCUMENTATION :: END
-