2016-03-21 04:26:02 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2021-03-11 07:48:11 +00:00
|
|
|
import re
|
2020-05-12 21:15:16 +00:00
|
|
|
from urllib.parse import quote, quote_plus, unquote, urlencode
|
|
|
|
|
2020-12-24 04:39:15 +00:00
|
|
|
from plexapi import X_PLEX_CONTAINER_SIZE, log, media, utils
|
2021-02-15 06:33:03 +00:00
|
|
|
from plexapi.base import OPERATORS, PlexObject
|
2016-03-31 20:52:48 +00:00
|
|
|
from plexapi.exceptions import BadRequest, NotFound
|
2020-03-16 15:12:28 +00:00
|
|
|
from plexapi.settings import Setting
|
2020-12-13 20:08:38 +00:00
|
|
|
from plexapi.utils import deprecated
|
2020-12-05 07:54:43 +00:00
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2017-02-06 04:52:10 +00:00
|
|
|
class Library(PlexObject):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Represents a PlexServer library. This contains all sections of media defined
|
|
|
|
in your Plex server including video, shows and audio.
|
|
|
|
|
|
|
|
Attributes:
|
2017-02-14 04:32:27 +00:00
|
|
|
key (str): '/library'
|
2017-01-26 04:21:13 +00:00
|
|
|
identifier (str): Unknown ('com.plexapp.plugins.library').
|
|
|
|
mediaTagVersion (str): Unknown (/system/bundle/media/flags/)
|
|
|
|
server (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to.
|
|
|
|
title1 (str): 'Plex Library' (not sure how useful this is).
|
|
|
|
title2 (str): Second title (this is blank on my setup).
|
|
|
|
"""
|
2017-02-06 04:52:10 +00:00
|
|
|
key = '/library'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
2017-02-04 08:08:47 +00:00
|
|
|
self._data = data
|
2017-02-06 04:52:10 +00:00
|
|
|
self._sectionsByID = {} # cached Section UUIDs
|
2014-12-29 03:21:58 +00:00
|
|
|
self.identifier = data.attrib.get('identifier')
|
|
|
|
self.mediaTagVersion = data.attrib.get('mediaTagVersion')
|
|
|
|
self.title1 = data.attrib.get('title1')
|
|
|
|
self.title2 = data.attrib.get('title2')
|
|
|
|
|
|
|
|
def sections(self):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Returns a list of all media sections in this library. Library sections may be any of
|
|
|
|
:class:`~plexapi.library.MovieSection`, :class:`~plexapi.library.ShowSection`,
|
|
|
|
:class:`~plexapi.library.MusicSection`, :class:`~plexapi.library.PhotoSection`.
|
|
|
|
"""
|
2017-02-06 04:52:10 +00:00
|
|
|
key = '/library/sections'
|
2017-02-13 02:55:55 +00:00
|
|
|
sections = []
|
2017-02-09 04:29:17 +00:00
|
|
|
for elem in self._server.query(key):
|
2017-02-13 02:55:55 +00:00
|
|
|
for cls in (MovieSection, ShowSection, MusicSection, PhotoSection):
|
|
|
|
if elem.attrib.get('type') == cls.TYPE:
|
|
|
|
section = cls(self._server, elem, key)
|
|
|
|
self._sectionsByID[section.key] = section
|
|
|
|
sections.append(section)
|
|
|
|
return sections
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def section(self, title=None):
|
2017-01-31 04:48:21 +00:00
|
|
|
""" Returns the :class:`~plexapi.library.LibrarySection` that matches the specified title.
|
2017-01-26 04:21:13 +00:00
|
|
|
|
|
|
|
Parameters:
|
|
|
|
title (str): Title of the section to return.
|
|
|
|
"""
|
2017-02-07 06:20:49 +00:00
|
|
|
for section in self.sections():
|
2017-02-13 02:55:55 +00:00
|
|
|
if section.title.lower() == title.lower():
|
2017-02-07 06:20:49 +00:00
|
|
|
return section
|
2014-12-29 03:21:58 +00:00
|
|
|
raise NotFound('Invalid library section: %s' % title)
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2016-04-12 02:43:21 +00:00
|
|
|
def sectionByID(self, sectionID):
|
2017-01-31 04:48:21 +00:00
|
|
|
""" Returns the :class:`~plexapi.library.LibrarySection` that matches the specified sectionID.
|
2017-01-31 00:02:22 +00:00
|
|
|
|
2017-01-26 04:21:13 +00:00
|
|
|
Parameters:
|
2017-02-03 16:39:46 +00:00
|
|
|
sectionID (str): ID of the section to return.
|
2017-01-26 04:21:13 +00:00
|
|
|
"""
|
2017-02-01 21:32:00 +00:00
|
|
|
if not self._sectionsByID or sectionID not in self._sectionsByID:
|
2016-04-12 02:43:21 +00:00
|
|
|
self.sections()
|
|
|
|
return self._sectionsByID[sectionID]
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2017-02-09 04:08:25 +00:00
|
|
|
def all(self, **kwargs):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Returns a list of all media from all library sections.
|
|
|
|
This may be a very large dataset to retrieve.
|
|
|
|
"""
|
2017-02-13 02:55:55 +00:00
|
|
|
items = []
|
|
|
|
for section in self.sections():
|
|
|
|
for item in section.all(**kwargs):
|
|
|
|
items.append(item)
|
|
|
|
return items
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def onDeck(self):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Returns a list of all media items on deck. """
|
2017-02-07 06:20:49 +00:00
|
|
|
return self.fetchItems('/library/onDeck')
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def recentlyAdded(self):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Returns a list of all media items recently added. """
|
2017-02-07 06:20:49 +00:00
|
|
|
return self.fetchItems('/library/recentlyAdded')
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2016-04-08 02:48:45 +00:00
|
|
|
def search(self, title=None, libtype=None, **kwargs):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Searching within a library section is much more powerful. It seems certain
|
|
|
|
attributes on the media objects can be targeted to filter this search down
|
|
|
|
a bit, but I havent found the documentation for it.
|
2016-12-15 23:06:12 +00:00
|
|
|
|
|
|
|
Example: "studio=Comedy%20Central" or "year=1999" "title=Kung Fu" all work. Other items
|
2017-01-26 04:21:13 +00:00
|
|
|
such as actor=<id> seem to work, but require you already know the id of the actor.
|
|
|
|
TLDR: This is untested but seems to work. Use library section search when you can.
|
2016-03-31 20:52:48 +00:00
|
|
|
"""
|
2016-01-19 09:50:31 +00:00
|
|
|
args = {}
|
2016-12-15 23:06:12 +00:00
|
|
|
if title:
|
2020-05-11 21:12:15 +00:00
|
|
|
args['title'] = title
|
2016-12-15 23:06:12 +00:00
|
|
|
if libtype:
|
|
|
|
args['type'] = utils.searchType(libtype)
|
2016-03-31 20:52:48 +00:00
|
|
|
for attr, value in kwargs.items():
|
|
|
|
args[attr] = value
|
2017-02-06 06:28:58 +00:00
|
|
|
key = '/library/all%s' % utils.joinArgs(args)
|
2017-02-07 06:20:49 +00:00
|
|
|
return self.fetchItems(key)
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
def cleanBundles(self):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Poster images and other metadata for items in your library are kept in "bundle"
|
|
|
|
packages. When you remove items from your library, these bundles aren't immediately
|
|
|
|
removed. Removing these old bundles can reduce the size of your install. By default, your
|
|
|
|
server will automatically clean up old bundles once a week as part of Scheduled Tasks.
|
|
|
|
"""
|
2017-02-02 03:53:05 +00:00
|
|
|
# TODO: Should this check the response for success or the correct mediaprefix?
|
2020-10-10 19:09:22 +00:00
|
|
|
self._server.query('/library/clean/bundles?async=1', method=self._server._session.put)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def emptyTrash(self):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" If a library has items in the Library Trash, use this option to empty the Trash. """
|
2014-12-29 03:21:58 +00:00
|
|
|
for section in self.sections():
|
|
|
|
section.emptyTrash()
|
|
|
|
|
|
|
|
def optimize(self):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" The Optimize option cleans up the server database from unused or fragmented data.
|
|
|
|
For example, if you have deleted or added an entire library or many items in a
|
|
|
|
library, you may like to optimize the database.
|
|
|
|
"""
|
2020-10-10 19:09:22 +00:00
|
|
|
self._server.query('/library/optimize?async=1', method=self._server._session.put)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2017-02-14 22:36:21 +00:00
|
|
|
def update(self):
|
|
|
|
""" Scan this library for new items."""
|
2017-02-09 04:29:17 +00:00
|
|
|
self._server.query('/library/sections/all/refresh')
|
2017-01-03 22:58:35 +00:00
|
|
|
|
2017-02-14 22:36:21 +00:00
|
|
|
def cancelUpdate(self):
|
2017-02-15 04:22:57 +00:00
|
|
|
""" Cancel a library update. """
|
|
|
|
key = '/library/sections/all/refresh'
|
|
|
|
self._server.query(key, method=self._server._session.delete)
|
2017-02-14 22:36:21 +00:00
|
|
|
|
|
|
|
def refresh(self):
|
2017-02-15 04:22:57 +00:00
|
|
|
""" Forces a download of fresh media information from the internet.
|
|
|
|
This can take a long time. Any locked fields are not modified.
|
|
|
|
"""
|
2017-02-14 22:36:21 +00:00
|
|
|
self._server.query('/library/sections/all/refresh?force=1')
|
|
|
|
|
2017-02-20 03:51:17 +00:00
|
|
|
def deleteMediaPreviews(self):
|
|
|
|
""" Delete the preview thumbnails for the all sections. This cannot be
|
|
|
|
undone. Recreating media preview files can take hours or even days.
|
2017-02-18 21:12:43 +00:00
|
|
|
"""
|
|
|
|
for section in self.sections():
|
2017-02-20 03:51:17 +00:00
|
|
|
section.deleteMediaPreviews()
|
2017-02-18 21:12:43 +00:00
|
|
|
|
2017-02-27 22:16:02 +00:00
|
|
|
def add(self, name='', type='', agent='', scanner='', location='', language='en', *args, **kwargs):
|
2017-02-27 12:23:35 +00:00
|
|
|
""" Simplified add for the most common options.
|
|
|
|
|
2017-03-22 03:04:23 +00:00
|
|
|
Parameters:
|
|
|
|
name (str): Name of the library
|
|
|
|
agent (str): Example com.plexapp.agents.imdb
|
|
|
|
type (str): movie, show, # check me
|
|
|
|
location (str): /path/to/files
|
|
|
|
language (str): Two letter language fx en
|
|
|
|
kwargs (dict): Advanced options should be passed as a dict. where the id is the key.
|
|
|
|
|
2017-04-23 05:18:53 +00:00
|
|
|
**Photo Preferences**
|
|
|
|
|
|
|
|
* **agent** (str): com.plexapp.agents.none
|
|
|
|
* **enableAutoPhotoTags** (bool): Tag photos. Default value false.
|
|
|
|
* **enableBIFGeneration** (bool): Enable video preview thumbnails. Default value true.
|
|
|
|
* **includeInGlobal** (bool): Include in dashboard. Default value true.
|
|
|
|
* **scanner** (str): Plex Photo Scanner
|
|
|
|
|
|
|
|
**Movie Preferences**
|
|
|
|
|
2021-01-25 02:03:58 +00:00
|
|
|
* **agent** (str): com.plexapp.agents.none, com.plexapp.agents.imdb, tv.plex.agents.movie,
|
|
|
|
com.plexapp.agents.themoviedb
|
2017-04-23 05:18:53 +00:00
|
|
|
* **enableBIFGeneration** (bool): Enable video preview thumbnails. Default value true.
|
|
|
|
* **enableCinemaTrailers** (bool): Enable Cinema Trailers. Default value true.
|
|
|
|
* **includeInGlobal** (bool): Include in dashboard. Default value true.
|
2021-01-25 02:03:58 +00:00
|
|
|
* **scanner** (str): Plex Movie, Plex Movie Scanner, Plex Video Files Scanner, Plex Video Files
|
2017-04-23 05:18:53 +00:00
|
|
|
|
|
|
|
**IMDB Movie Options** (com.plexapp.agents.imdb)
|
|
|
|
|
|
|
|
* **title** (bool): Localized titles. Default value false.
|
|
|
|
* **extras** (bool): Find trailers and extras automatically (Plex Pass required). Default value true.
|
|
|
|
* **only_trailers** (bool): Skip extras which aren't trailers. Default value false.
|
|
|
|
* **redband** (bool): Use red band (restricted audiences) trailers when available. Default value false.
|
|
|
|
* **native_subs** (bool): Include extras with subtitles in Library language. Default value false.
|
|
|
|
* **cast_list** (int): Cast List Source: Default value 1 Possible options: 0:IMDb,1:The Movie Database.
|
|
|
|
* **ratings** (int): Ratings Source, Default value 0 Possible options:
|
|
|
|
0:Rotten Tomatoes, 1:IMDb, 2:The Movie Database.
|
|
|
|
* **summary** (int): Plot Summary Source: Default value 1 Possible options: 0:IMDb,1:The Movie Database.
|
2017-04-29 06:21:20 +00:00
|
|
|
* **country** (int): Default value 46 Possible options 0:Argentina, 1:Australia, 2:Austria,
|
|
|
|
3:Belgium, 4:Belize, 5:Bolivia, 6:Brazil, 7:Canada, 8:Chile, 9:Colombia, 10:Costa Rica,
|
|
|
|
11:Czech Republic, 12:Denmark, 13:Dominican Republic, 14:Ecuador, 15:El Salvador,
|
|
|
|
16:France, 17:Germany, 18:Guatemala, 19:Honduras, 20:Hong Kong SAR, 21:Ireland,
|
|
|
|
22:Italy, 23:Jamaica, 24:Korea, 25:Liechtenstein, 26:Luxembourg, 27:Mexico, 28:Netherlands,
|
|
|
|
29:New Zealand, 30:Nicaragua, 31:Panama, 32:Paraguay, 33:Peru, 34:Portugal,
|
|
|
|
35:Peoples Republic of China, 36:Puerto Rico, 37:Russia, 38:Singapore, 39:South Africa,
|
|
|
|
40:Spain, 41:Sweden, 42:Switzerland, 43:Taiwan, 44:Trinidad, 45:United Kingdom,
|
2017-04-23 05:18:53 +00:00
|
|
|
46:United States, 47:Uruguay, 48:Venezuela.
|
|
|
|
* **collections** (bool): Use collection info from The Movie Database. Default value false.
|
|
|
|
* **localart** (bool): Prefer artwork based on library language. Default value true.
|
|
|
|
* **adult** (bool): Include adult content. Default value false.
|
|
|
|
* **usage** (bool): Send anonymous usage data to Plex. Default value true.
|
|
|
|
|
|
|
|
**TheMovieDB Movie Options** (com.plexapp.agents.themoviedb)
|
|
|
|
|
|
|
|
* **collections** (bool): Use collection info from The Movie Database. Default value false.
|
|
|
|
* **localart** (bool): Prefer artwork based on library language. Default value true.
|
|
|
|
* **adult** (bool): Include adult content. Default value false.
|
|
|
|
* **country** (int): Country (used for release date and content rating). Default value 47 Possible
|
2017-04-29 06:21:20 +00:00
|
|
|
options 0:, 1:Argentina, 2:Australia, 3:Austria, 4:Belgium, 5:Belize, 6:Bolivia, 7:Brazil, 8:Canada,
|
|
|
|
9:Chile, 10:Colombia, 11:Costa Rica, 12:Czech Republic, 13:Denmark, 14:Dominican Republic, 15:Ecuador,
|
|
|
|
16:El Salvador, 17:France, 18:Germany, 19:Guatemala, 20:Honduras, 21:Hong Kong SAR, 22:Ireland,
|
|
|
|
23:Italy, 24:Jamaica, 25:Korea, 26:Liechtenstein, 27:Luxembourg, 28:Mexico, 29:Netherlands,
|
|
|
|
30:New Zealand, 31:Nicaragua, 32:Panama, 33:Paraguay, 34:Peru, 35:Portugal,
|
|
|
|
36:Peoples Republic of China, 37:Puerto Rico, 38:Russia, 39:Singapore, 40:South Africa, 41:Spain,
|
|
|
|
42:Sweden, 43:Switzerland, 44:Taiwan, 45:Trinidad, 46:United Kingdom, 47:United States, 48:Uruguay,
|
|
|
|
49:Venezuela.
|
2017-04-23 05:18:53 +00:00
|
|
|
|
|
|
|
**Show Preferences**
|
|
|
|
|
2021-03-11 17:19:54 +00:00
|
|
|
* **agent** (str): com.plexapp.agents.none, com.plexapp.agents.thetvdb, com.plexapp.agents.themoviedb,
|
|
|
|
tv.plex.agent.series
|
2017-04-23 05:18:53 +00:00
|
|
|
* **enableBIFGeneration** (bool): Enable video preview thumbnails. Default value true.
|
2017-04-29 06:21:20 +00:00
|
|
|
* **episodeSort** (int): Episode order. Default -1 Possible options: 0:Oldest first, 1:Newest first.
|
2017-04-23 05:18:53 +00:00
|
|
|
* **flattenSeasons** (int): Seasons. Default value 0 Possible options: 0:Show,1:Hide.
|
|
|
|
* **includeInGlobal** (bool): Include in dashboard. Default value true.
|
2021-03-11 17:19:54 +00:00
|
|
|
* **scanner** (str): Plex TV Series, Plex Series Scanner
|
2017-04-23 05:18:53 +00:00
|
|
|
|
|
|
|
**TheTVDB Show Options** (com.plexapp.agents.thetvdb)
|
|
|
|
|
|
|
|
* **extras** (bool): Find trailers and extras automatically (Plex Pass required). Default value true.
|
|
|
|
* **native_subs** (bool): Include extras with subtitles in Library language. Default value false.
|
|
|
|
|
|
|
|
**TheMovieDB Show Options** (com.plexapp.agents.themoviedb)
|
|
|
|
|
|
|
|
* **collections** (bool): Use collection info from The Movie Database. Default value false.
|
|
|
|
* **localart** (bool): Prefer artwork based on library language. Default value true.
|
|
|
|
* **adult** (bool): Include adult content. Default value false.
|
2017-04-29 06:21:20 +00:00
|
|
|
* **country** (int): Country (used for release date and content rating). Default value 47 options
|
|
|
|
0:, 1:Argentina, 2:Australia, 3:Austria, 4:Belgium, 5:Belize, 6:Bolivia, 7:Brazil, 8:Canada, 9:Chile,
|
|
|
|
10:Colombia, 11:Costa Rica, 12:Czech Republic, 13:Denmark, 14:Dominican Republic, 15:Ecuador,
|
|
|
|
16:El Salvador, 17:France, 18:Germany, 19:Guatemala, 20:Honduras, 21:Hong Kong SAR, 22:Ireland,
|
|
|
|
23:Italy, 24:Jamaica, 25:Korea, 26:Liechtenstein, 27:Luxembourg, 28:Mexico, 29:Netherlands,
|
|
|
|
30:New Zealand, 31:Nicaragua, 32:Panama, 33:Paraguay, 34:Peru, 35:Portugal,
|
|
|
|
36:Peoples Republic of China, 37:Puerto Rico, 38:Russia, 39:Singapore, 40:South Africa,
|
|
|
|
41:Spain, 42:Sweden, 43:Switzerland, 44:Taiwan, 45:Trinidad, 46:United Kingdom, 47:United States,
|
|
|
|
48:Uruguay, 49:Venezuela.
|
2017-04-23 05:18:53 +00:00
|
|
|
|
|
|
|
**Other Video Preferences**
|
|
|
|
|
|
|
|
* **agent** (str): com.plexapp.agents.none, com.plexapp.agents.imdb, com.plexapp.agents.themoviedb
|
|
|
|
* **enableBIFGeneration** (bool): Enable video preview thumbnails. Default value true.
|
|
|
|
* **enableCinemaTrailers** (bool): Enable Cinema Trailers. Default value true.
|
|
|
|
* **includeInGlobal** (bool): Include in dashboard. Default value true.
|
|
|
|
* **scanner** (str): Plex Movie Scanner, Plex Video Files Scanner
|
|
|
|
|
|
|
|
**IMDB Other Video Options** (com.plexapp.agents.imdb)
|
|
|
|
|
|
|
|
* **title** (bool): Localized titles. Default value false.
|
|
|
|
* **extras** (bool): Find trailers and extras automatically (Plex Pass required). Default value true.
|
|
|
|
* **only_trailers** (bool): Skip extras which aren't trailers. Default value false.
|
|
|
|
* **redband** (bool): Use red band (restricted audiences) trailers when available. Default value false.
|
|
|
|
* **native_subs** (bool): Include extras with subtitles in Library language. Default value false.
|
|
|
|
* **cast_list** (int): Cast List Source: Default value 1 Possible options: 0:IMDb,1:The Movie Database.
|
|
|
|
* **ratings** (int): Ratings Source Default value 0 Possible options:
|
|
|
|
0:Rotten Tomatoes,1:IMDb,2:The Movie Database.
|
|
|
|
* **summary** (int): Plot Summary Source: Default value 1 Possible options: 0:IMDb,1:The Movie Database.
|
2017-04-29 06:21:20 +00:00
|
|
|
* **country** (int): Country: Default value 46 Possible options: 0:Argentina, 1:Australia, 2:Austria,
|
|
|
|
3:Belgium, 4:Belize, 5:Bolivia, 6:Brazil, 7:Canada, 8:Chile, 9:Colombia, 10:Costa Rica,
|
|
|
|
11:Czech Republic, 12:Denmark, 13:Dominican Republic, 14:Ecuador, 15:El Salvador, 16:France,
|
|
|
|
17:Germany, 18:Guatemala, 19:Honduras, 20:Hong Kong SAR, 21:Ireland, 22:Italy, 23:Jamaica,
|
|
|
|
24:Korea, 25:Liechtenstein, 26:Luxembourg, 27:Mexico, 28:Netherlands, 29:New Zealand, 30:Nicaragua,
|
|
|
|
31:Panama, 32:Paraguay, 33:Peru, 34:Portugal, 35:Peoples Republic of China, 36:Puerto Rico,
|
|
|
|
37:Russia, 38:Singapore, 39:South Africa, 40:Spain, 41:Sweden, 42:Switzerland, 43:Taiwan, 44:Trinidad,
|
|
|
|
45:United Kingdom, 46:United States, 47:Uruguay, 48:Venezuela.
|
2017-04-23 05:18:53 +00:00
|
|
|
* **collections** (bool): Use collection info from The Movie Database. Default value false.
|
|
|
|
* **localart** (bool): Prefer artwork based on library language. Default value true.
|
|
|
|
* **adult** (bool): Include adult content. Default value false.
|
|
|
|
* **usage** (bool): Send anonymous usage data to Plex. Default value true.
|
|
|
|
|
|
|
|
**TheMovieDB Other Video Options** (com.plexapp.agents.themoviedb)
|
|
|
|
|
|
|
|
* **collections** (bool): Use collection info from The Movie Database. Default value false.
|
|
|
|
* **localart** (bool): Prefer artwork based on library language. Default value true.
|
|
|
|
* **adult** (bool): Include adult content. Default value false.
|
|
|
|
* **country** (int): Country (used for release date and content rating). Default
|
2017-04-29 06:21:20 +00:00
|
|
|
value 47 Possible options 0:, 1:Argentina, 2:Australia, 3:Austria, 4:Belgium, 5:Belize,
|
|
|
|
6:Bolivia, 7:Brazil, 8:Canada, 9:Chile, 10:Colombia, 11:Costa Rica, 12:Czech Republic,
|
|
|
|
13:Denmark, 14:Dominican Republic, 15:Ecuador, 16:El Salvador, 17:France, 18:Germany,
|
|
|
|
19:Guatemala, 20:Honduras, 21:Hong Kong SAR, 22:Ireland, 23:Italy, 24:Jamaica,
|
|
|
|
25:Korea, 26:Liechtenstein, 27:Luxembourg, 28:Mexico, 29:Netherlands, 30:New Zealand,
|
|
|
|
31:Nicaragua, 32:Panama, 33:Paraguay, 34:Peru, 35:Portugal,
|
|
|
|
36:Peoples Republic of China, 37:Puerto Rico, 38:Russia, 39:Singapore,
|
|
|
|
40:South Africa, 41:Spain, 42:Sweden, 43:Switzerland, 44:Taiwan, 45:Trinidad,
|
2017-04-23 05:18:53 +00:00
|
|
|
46:United Kingdom, 47:United States, 48:Uruguay, 49:Venezuela.
|
2017-02-27 12:23:35 +00:00
|
|
|
"""
|
2017-02-27 21:04:37 +00:00
|
|
|
part = '/library/sections?name=%s&type=%s&agent=%s&scanner=%s&language=%s&location=%s' % (
|
2017-04-23 05:18:53 +00:00
|
|
|
quote_plus(name), type, agent, quote_plus(scanner), language, quote_plus(location)) # noqa E126
|
2017-02-27 21:04:37 +00:00
|
|
|
if kwargs:
|
2017-02-27 12:23:35 +00:00
|
|
|
part += urlencode(kwargs)
|
2017-02-27 21:04:37 +00:00
|
|
|
return self._server.query(part, method=self._server._session.post)
|
2017-02-27 12:23:35 +00:00
|
|
|
|
2019-11-20 11:50:25 +00:00
|
|
|
def history(self, maxresults=9999999, mindate=None):
|
|
|
|
""" Get Play History for all library Sections for the owner.
|
|
|
|
Parameters:
|
|
|
|
maxresults (int): Only return the specified number of results (optional).
|
|
|
|
mindate (datetime): Min datetime to return results from.
|
|
|
|
"""
|
2019-11-16 21:35:20 +00:00
|
|
|
hist = []
|
|
|
|
for section in self.sections():
|
2019-11-20 11:50:25 +00:00
|
|
|
hist.extend(section.history(maxresults=maxresults, mindate=mindate))
|
2019-11-16 21:35:20 +00:00
|
|
|
return hist
|
|
|
|
|
2017-02-27 12:23:35 +00:00
|
|
|
|
2017-02-06 04:52:10 +00:00
|
|
|
class LibrarySection(PlexObject):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Base class for a single library section.
|
|
|
|
|
|
|
|
Attributes:
|
2020-12-24 04:39:45 +00:00
|
|
|
agent (str): The metadata agent used for the library section (com.plexapp.agents.imdb, etc).
|
|
|
|
allowSync (bool): True if you allow syncing content from the library section.
|
|
|
|
art (str): Background artwork used to respresent the library section.
|
|
|
|
composite (str): Composite image used to represent the library section.
|
|
|
|
createdAt (datetime): Datetime the library section was created.
|
2021-03-11 05:53:24 +00:00
|
|
|
filters (bool): True if filters are available for the library section.
|
|
|
|
key (int): Key (or ID) of this library section.
|
2017-01-26 04:21:13 +00:00
|
|
|
language (str): Language represented in this section (en, xn, etc).
|
2020-12-24 04:39:45 +00:00
|
|
|
locations (List<str>): List of folder paths added to the library section.
|
|
|
|
refreshing (bool): True if this section is currently being refreshed.
|
2017-01-26 04:21:13 +00:00
|
|
|
scanner (str): Internal scanner used to find media (Plex Movie Scanner, Plex Premium Music Scanner, etc.)
|
2020-12-24 04:39:45 +00:00
|
|
|
thumb (str): Thumbnail image used to represent the library section.
|
|
|
|
title (str): Name of the library section.
|
|
|
|
type (str): Type of content section represents (movie, show, artist, photo).
|
|
|
|
updatedAt (datetime): Datetime the library section was last updated.
|
|
|
|
uuid (str): Unique id for the section (32258d7c-3e6c-4ac5-98ad-bad7a3b78c63)
|
2017-01-26 04:21:13 +00:00
|
|
|
"""
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2017-02-06 04:52:10 +00:00
|
|
|
def _loadData(self, data):
|
2017-02-04 08:08:47 +00:00
|
|
|
self._data = data
|
2016-04-12 02:43:21 +00:00
|
|
|
self.agent = data.attrib.get('agent')
|
|
|
|
self.allowSync = utils.cast(bool, data.attrib.get('allowSync'))
|
|
|
|
self.art = data.attrib.get('art')
|
|
|
|
self.composite = data.attrib.get('composite')
|
|
|
|
self.createdAt = utils.toDatetime(data.attrib.get('createdAt'))
|
2021-03-11 05:53:24 +00:00
|
|
|
self.filters = utils.cast(bool, data.attrib.get('filters'))
|
|
|
|
self.key = utils.cast(int, data.attrib.get('key'))
|
2014-12-29 03:21:58 +00:00
|
|
|
self.language = data.attrib.get('language')
|
2017-02-13 03:15:47 +00:00
|
|
|
self.locations = self.listAttrs(data, 'path', etag='Location')
|
2016-04-12 02:43:21 +00:00
|
|
|
self.refreshing = utils.cast(bool, data.attrib.get('refreshing'))
|
|
|
|
self.scanner = data.attrib.get('scanner')
|
|
|
|
self.thumb = data.attrib.get('thumb')
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
|
|
|
|
self.uuid = data.attrib.get('uuid')
|
2020-04-26 19:18:52 +00:00
|
|
|
# Private attrs as we dont want a reload.
|
2021-03-11 07:48:11 +00:00
|
|
|
self._filterTypes = None
|
|
|
|
self._fieldTypes = None
|
2020-04-26 19:18:52 +00:00
|
|
|
self._total_size = None
|
|
|
|
|
2020-04-27 16:22:10 +00:00
|
|
|
def fetchItems(self, ekey, cls=None, container_start=None, container_size=None, **kwargs):
|
2020-04-26 20:51:57 +00:00
|
|
|
""" Load the specified key to find and build all items with the specified tag
|
|
|
|
and attrs. See :func:`~plexapi.base.PlexObject.fetchItem` for more details
|
|
|
|
on how this is used.
|
|
|
|
|
2020-04-27 16:22:10 +00:00
|
|
|
Parameters:
|
|
|
|
container_start (None, int): offset to get a subset of the data
|
|
|
|
container_size (None, int): How many items in data
|
|
|
|
|
2020-04-26 20:51:57 +00:00
|
|
|
"""
|
|
|
|
url_kw = {}
|
2020-04-27 16:22:10 +00:00
|
|
|
if container_start is not None:
|
|
|
|
url_kw["X-Plex-Container-Start"] = container_start
|
|
|
|
if container_size is not None:
|
|
|
|
url_kw["X-Plex-Container-Size"] = container_size
|
2020-04-26 20:51:57 +00:00
|
|
|
|
|
|
|
if ekey is None:
|
|
|
|
raise BadRequest('ekey was not provided')
|
|
|
|
data = self._server.query(ekey, params=url_kw)
|
|
|
|
|
2020-04-26 21:14:00 +00:00
|
|
|
if '/all' in ekey:
|
2020-04-29 11:42:19 +00:00
|
|
|
# totalSize is only included in the xml response
|
|
|
|
# if container size is used.
|
|
|
|
total_size = data.attrib.get("totalSize") or data.attrib.get("size")
|
|
|
|
self._total_size = utils.cast(int, total_size)
|
2020-04-26 21:14:00 +00:00
|
|
|
|
2020-04-26 20:51:57 +00:00
|
|
|
items = self.findItems(data, cls, ekey, **kwargs)
|
|
|
|
|
|
|
|
librarySectionID = data.attrib.get('librarySectionID')
|
|
|
|
if librarySectionID:
|
|
|
|
for item in items:
|
|
|
|
item.librarySectionID = librarySectionID
|
|
|
|
return items
|
|
|
|
|
2020-04-26 19:18:52 +00:00
|
|
|
@property
|
|
|
|
def totalSize(self):
|
2020-12-24 17:08:52 +00:00
|
|
|
""" Returns the total number of items in the library. """
|
2020-04-26 19:18:52 +00:00
|
|
|
if self._total_size is None:
|
|
|
|
part = '/library/sections/%s/all?X-Plex-Container-Start=0&X-Plex-Container-Size=1' % self.key
|
|
|
|
data = self._server.query(part)
|
|
|
|
self._total_size = int(data.attrib.get("totalSize"))
|
|
|
|
|
|
|
|
return self._total_size
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2017-02-10 23:16:23 +00:00
|
|
|
def delete(self):
|
2017-03-22 03:04:23 +00:00
|
|
|
""" Delete a library section. """
|
2017-02-10 23:16:23 +00:00
|
|
|
try:
|
|
|
|
return self._server.query('/library/sections/%s' % self.key, method=self._server._session.delete)
|
2017-02-10 23:16:34 +00:00
|
|
|
except BadRequest: # pragma: no cover
|
2017-02-13 03:38:56 +00:00
|
|
|
msg = 'Failed to delete library %s' % self.key
|
|
|
|
msg += 'You may need to allow this permission in your Plex settings.'
|
|
|
|
log.error(msg)
|
2017-02-10 23:16:23 +00:00
|
|
|
raise
|
2017-02-08 07:00:43 +00:00
|
|
|
|
2020-04-15 18:41:15 +00:00
|
|
|
def reload(self, key=None):
|
2020-04-15 23:01:45 +00:00
|
|
|
return self._server.library.section(self.title)
|
2020-04-15 18:41:15 +00:00
|
|
|
|
2020-03-16 15:56:16 +00:00
|
|
|
def edit(self, agent=None, **kwargs):
|
2017-04-23 05:18:53 +00:00
|
|
|
""" Edit a library (Note: agent is required). See :class:`~plexapi.library.Library` for example usage.
|
2017-10-03 18:39:28 +00:00
|
|
|
|
2017-03-22 03:04:23 +00:00
|
|
|
Parameters:
|
|
|
|
kwargs (dict): Dict of settings to edit.
|
2017-02-27 21:04:37 +00:00
|
|
|
"""
|
2020-03-16 15:56:16 +00:00
|
|
|
if not agent:
|
|
|
|
agent = self.agent
|
|
|
|
part = '/library/sections/%s?agent=%s&%s' % (self.key, agent, urlencode(kwargs))
|
2017-02-27 22:16:02 +00:00
|
|
|
self._server.query(part, method=self._server._session.put)
|
2017-02-27 22:20:10 +00:00
|
|
|
|
2017-02-27 22:16:02 +00:00
|
|
|
# Reload this way since the self.key dont have a full path, but is simply a id.
|
|
|
|
for s in self._server.library.sections():
|
|
|
|
if s.key == self.key:
|
|
|
|
return s
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
def get(self, title):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Returns the media item with the specified title.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
title (str): Title of the item to return.
|
|
|
|
"""
|
2020-05-11 21:12:15 +00:00
|
|
|
key = '/library/sections/%s/all?title=%s' % (self.key, quote(title, safe=''))
|
2017-02-09 06:54:38 +00:00
|
|
|
return self.fetchItem(key, title__iexact=title)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2020-12-23 23:45:21 +00:00
|
|
|
def all(self, libtype=None, **kwargs):
|
|
|
|
""" Returns a list of all items from this library section.
|
|
|
|
See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting.
|
2018-12-21 19:51:39 +00:00
|
|
|
"""
|
2020-12-27 04:53:55 +00:00
|
|
|
libtype = libtype or self.TYPE
|
2020-12-23 23:45:21 +00:00
|
|
|
return self.search(libtype=libtype, **kwargs)
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2020-07-02 05:33:36 +00:00
|
|
|
def folders(self):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Returns a list of available :class:`~plexapi.library.Folder` for this library section.
|
2020-07-02 05:33:36 +00:00
|
|
|
"""
|
|
|
|
key = '/library/sections/%s/folder' % self.key
|
|
|
|
return self.fetchItems(key, Folder)
|
|
|
|
|
2020-06-22 19:04:33 +00:00
|
|
|
def hubs(self):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Returns a list of available :class:`~plexapi.library.Hub` for this library section.
|
2020-06-22 19:04:33 +00:00
|
|
|
"""
|
|
|
|
key = '/hubs/sections/%s' % self.key
|
|
|
|
return self.fetchItems(key)
|
|
|
|
|
2020-03-16 17:54:16 +00:00
|
|
|
def agents(self):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Returns a list of available :class:`~plexapi.media.Agent` for this library section.
|
2020-03-16 17:54:16 +00:00
|
|
|
"""
|
2020-03-16 18:26:01 +00:00
|
|
|
return self._server.agents(utils.searchType(self.type))
|
2020-03-16 17:54:16 +00:00
|
|
|
|
2020-03-16 15:12:28 +00:00
|
|
|
def settings(self):
|
|
|
|
""" Returns a list of all library settings. """
|
|
|
|
key = '/library/sections/%s/prefs' % self.key
|
|
|
|
data = self._server.query(key)
|
|
|
|
return self.findItems(data, cls=Setting)
|
|
|
|
|
2020-06-10 03:25:23 +00:00
|
|
|
def editAdvanced(self, **kwargs):
|
|
|
|
""" Edit a library's advanced settings. """
|
|
|
|
data = {}
|
|
|
|
idEnums = {}
|
|
|
|
key = 'prefs[%s]'
|
|
|
|
|
|
|
|
for setting in self.settings():
|
|
|
|
if setting.type != 'bool':
|
|
|
|
idEnums[setting.id] = setting.enumValues
|
|
|
|
else:
|
|
|
|
idEnums[setting.id] = {0: False, 1: True}
|
|
|
|
|
|
|
|
for settingID, value in kwargs.items():
|
|
|
|
try:
|
|
|
|
enums = idEnums.get(settingID)
|
|
|
|
enumValues = [int(x) for x in enums]
|
|
|
|
except TypeError:
|
|
|
|
raise NotFound('%s not found in %s' % (value, list(idEnums.keys())))
|
|
|
|
if value in enumValues:
|
|
|
|
data[key % settingID] = value
|
|
|
|
else:
|
|
|
|
raise NotFound('%s not found in %s' % (value, enums))
|
|
|
|
|
|
|
|
self.edit(**data)
|
|
|
|
|
2020-06-10 03:32:22 +00:00
|
|
|
def defaultAdvanced(self):
|
|
|
|
""" Edit all of library's advanced settings to default. """
|
|
|
|
data = {}
|
|
|
|
key = 'prefs[%s]'
|
|
|
|
for setting in self.settings():
|
2020-06-18 14:03:47 +00:00
|
|
|
if setting.type == 'bool':
|
|
|
|
data[key % setting.id] = int(setting.default)
|
|
|
|
else:
|
|
|
|
data[key % setting.id] = setting.default
|
2020-06-10 03:32:22 +00:00
|
|
|
|
|
|
|
self.edit(**data)
|
|
|
|
|
2020-09-21 21:06:14 +00:00
|
|
|
def timeline(self):
|
|
|
|
""" Returns a timeline query for this library section. """
|
|
|
|
key = '/library/sections/%s/timeline' % self.key
|
|
|
|
data = self._server.query(key)
|
|
|
|
return LibraryTimeline(self, data)
|
|
|
|
|
2016-03-31 20:52:48 +00:00
|
|
|
def onDeck(self):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Returns a list of media items on deck from this library section. """
|
2017-02-07 06:20:49 +00:00
|
|
|
key = '/library/sections/%s/onDeck' % self.key
|
|
|
|
return self.fetchItems(key)
|
2016-05-24 01:41:02 +00:00
|
|
|
|
|
|
|
def recentlyAdded(self, maxresults=50):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Returns a list of media items recently added from this library section.
|
2017-01-31 00:02:22 +00:00
|
|
|
|
2017-01-26 04:21:13 +00:00
|
|
|
Parameters:
|
|
|
|
maxresults (int): Max number of items to return (default 50).
|
|
|
|
"""
|
2016-05-24 01:41:02 +00:00
|
|
|
return self.search(sort='addedAt:desc', maxresults=maxresults)
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2020-06-30 13:00:50 +00:00
|
|
|
def firstCharacter(self):
|
|
|
|
key = '/library/sections/%s/firstCharacter' % self.key
|
|
|
|
return self.fetchItems(key, cls=FirstCharacter)
|
|
|
|
|
2016-04-01 03:39:09 +00:00
|
|
|
def analyze(self):
|
2017-02-09 20:01:23 +00:00
|
|
|
""" Run an analysis on all of the items in this library section. See
|
|
|
|
See :func:`~plexapi.base.PlexPartialObject.analyze` for more details.
|
|
|
|
"""
|
2017-02-07 06:20:49 +00:00
|
|
|
key = '/library/sections/%s/analyze' % self.key
|
2017-02-09 04:29:17 +00:00
|
|
|
self._server.query(key, method=self._server._session.put)
|
2016-04-01 03:39:09 +00:00
|
|
|
|
|
|
|
def emptyTrash(self):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" If a section has items in the Trash, use this option to empty the Trash. """
|
2017-02-07 06:20:49 +00:00
|
|
|
key = '/library/sections/%s/emptyTrash' % self.key
|
2017-11-24 23:48:32 +00:00
|
|
|
self._server.query(key, method=self._server._session.put)
|
2016-04-01 03:39:09 +00:00
|
|
|
|
2020-11-21 01:05:57 +00:00
|
|
|
def update(self, path=None):
|
|
|
|
""" Scan this section for new media.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
path (str, optional): Full path to folder to scan.
|
|
|
|
"""
|
2017-02-14 22:36:21 +00:00
|
|
|
key = '/library/sections/%s/refresh' % self.key
|
2020-11-21 01:05:57 +00:00
|
|
|
if path is not None:
|
|
|
|
key += '?path=%s' % quote_plus(path)
|
2017-02-14 22:36:21 +00:00
|
|
|
self._server.query(key)
|
|
|
|
|
|
|
|
def cancelUpdate(self):
|
2017-02-15 04:22:57 +00:00
|
|
|
""" Cancel update of this Library Section. """
|
2017-02-07 06:20:49 +00:00
|
|
|
key = '/library/sections/%s/refresh' % self.key
|
2017-02-14 22:36:21 +00:00
|
|
|
self._server.query(key, method=self._server._session.delete)
|
|
|
|
|
|
|
|
def refresh(self):
|
2017-02-15 04:22:57 +00:00
|
|
|
""" Forces a download of fresh media information from the internet.
|
|
|
|
This can take a long time. Any locked fields are not modified.
|
|
|
|
"""
|
2017-02-14 22:36:21 +00:00
|
|
|
key = '/library/sections/%s/refresh?force=1' % self.key
|
2017-02-09 04:29:17 +00:00
|
|
|
self._server.query(key)
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2017-02-20 03:51:17 +00:00
|
|
|
def deleteMediaPreviews(self):
|
|
|
|
""" Delete the preview thumbnails for items in this library. This cannot
|
|
|
|
be undone. Recreating media preview files can take hours or even days.
|
2017-02-18 21:12:43 +00:00
|
|
|
"""
|
|
|
|
key = '/library/sections/%s/indexes' % self.key
|
|
|
|
self._server.query(key, method=self._server._session.delete)
|
|
|
|
|
2021-03-11 07:48:11 +00:00
|
|
|
def _loadFilters(self):
|
|
|
|
""" Retrieves and caches the :class:`~plexapi.library.FilteringType` and
|
|
|
|
:class:`~plexapi.library.FilteringFieldType` for this library section.
|
|
|
|
"""
|
|
|
|
key = '/library/sections/%s/all?includeMeta=1&X-Plex-Container-Start=0&X-Plex-Container-Size=0' % self.key
|
|
|
|
data = self._server.query(key)
|
|
|
|
meta = data.find('Meta')
|
|
|
|
if meta:
|
|
|
|
self._filterTypes = self.findItems(meta, FilteringType)
|
|
|
|
self._fieldTypes = self.findItems(meta, FilteringFieldType)
|
|
|
|
|
|
|
|
def filterTypes(self):
|
|
|
|
""" Returns a list of available :class:`~plexapi.library.FilteringType` for this library section. """
|
|
|
|
if self._filterTypes is None:
|
|
|
|
self._loadFilters()
|
|
|
|
return self._filterTypes
|
|
|
|
|
|
|
|
def getFilterType(self, libtype=None):
|
|
|
|
""" Returns a :class:`~plexapi.library.FilteringType` for a specified libtype.
|
2017-01-26 04:21:13 +00:00
|
|
|
|
|
|
|
Parameters:
|
2021-03-11 07:48:11 +00:00
|
|
|
libtype (str, optional): The library type to filter (movie, show, season, episode,
|
|
|
|
artist, album, track, photoalbum, photo).
|
2017-01-26 04:21:13 +00:00
|
|
|
|
|
|
|
Raises:
|
2021-03-11 07:48:11 +00:00
|
|
|
:exc:`~plexapi.exceptions.BadRequest`: Invalid libtype for this library.
|
2014-12-29 03:21:58 +00:00
|
|
|
"""
|
2021-03-11 07:48:11 +00:00
|
|
|
libtype = libtype or self.TYPE
|
|
|
|
try:
|
|
|
|
return next(f for f in self.filterTypes() if f.type == libtype)
|
|
|
|
except StopIteration:
|
|
|
|
raise BadRequest('Invalid libtype for this library: %s' % libtype) from None
|
|
|
|
|
2021-03-11 21:04:04 +00:00
|
|
|
def fieldTypes(self):
|
|
|
|
""" Returns a list of available :class:`~plexapi.library.FilteringFieldType` for this library section. """
|
|
|
|
if self._fieldTypes is None:
|
|
|
|
self._loadFilters()
|
|
|
|
return self._fieldTypes
|
|
|
|
|
|
|
|
def getFieldType(self, fieldType):
|
|
|
|
""" Returns a :class:`~plexapi.library.FilteringFieldType` for a specified fieldType.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
fieldType (str): The data type for the field (tag, integer, string, boolean, date,
|
|
|
|
subtitleLanguage, audioLanguage, resolution).
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
:exc:`~plexapi.exceptions.BadRequest`: Invalid libtype for this library.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return next(f for f in self.fieldTypes() if f.type == fieldType)
|
|
|
|
except StopIteration:
|
|
|
|
raise BadRequest('Invalid fieldType for this library: %s' % fieldType) from None
|
|
|
|
|
2021-03-11 07:48:11 +00:00
|
|
|
def listFilters(self, libtype=None):
|
|
|
|
""" Returns a list of available :class:`~plexapi.library.FilteringFilter` for a specified libtype.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
libtype (str, optional): The library type to filter (movie, show, season, episode,
|
|
|
|
artist, album, track, photoalbum, photo).
|
|
|
|
"""
|
|
|
|
return self.getFilterType(libtype).filters
|
|
|
|
|
|
|
|
def listSorts(self, libtype=None):
|
|
|
|
""" Returns a list of available :class:`~plexapi.library.FilteringSort` for a specified libtype.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
libtype (str, optional): The library type to filter (movie, show, season, episode,
|
|
|
|
artist, album, track, photoalbum, photo).
|
|
|
|
"""
|
|
|
|
return self.getFilterType(libtype).sorts
|
|
|
|
|
|
|
|
def listFields(self, libtype=None):
|
|
|
|
""" Returns a list of available :class:`~plexapi.library.FilteringFields` for a specified libtype.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
libtype (str, optional): The library type to filter (movie, show, season, episode,
|
|
|
|
artist, album, track, photoalbum, photo).
|
|
|
|
"""
|
|
|
|
return self.getFilterType(libtype).fields
|
|
|
|
|
2021-03-11 21:04:04 +00:00
|
|
|
def listOperators(self, fieldType):
|
|
|
|
""" Returns a list of available :class:`~plexapi.library.FilteringOperator` for a specified fieldType.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
fieldType (str): The data type for the field (tag, integer, string, boolean, date,
|
|
|
|
subtitleLanguage, audioLanguage, resolution).
|
|
|
|
"""
|
|
|
|
return self.getFieldType(fieldType).operators
|
|
|
|
|
2021-03-11 07:48:11 +00:00
|
|
|
def listFilterChoices(self, field, libtype=None):
|
|
|
|
""" Returns a list of available :class:`~plexapi.library.FilterChoice` for a specified
|
|
|
|
:class:`~plexapi.library.FilteringFilter` or filter field.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
field (str): :class:`~plexapi.library.FilteringFilter` object,
|
|
|
|
or the name of the field (genre, year, contentRating, etc.).
|
|
|
|
libtype (str, optional): The library type to filter (movie, show, season, episode,
|
|
|
|
artist, album, track, photoalbum, photo).
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
:exc:`~plexapi.exceptions.BadRequest`: Unknown filter field.
|
|
|
|
"""
|
|
|
|
if isinstance(field, str):
|
|
|
|
try:
|
|
|
|
field = next(f for f in self.listFilters(libtype) if f.filter == field)
|
|
|
|
except StopIteration:
|
|
|
|
raise BadRequest('Unknown filter field: %s' % field) from None
|
|
|
|
|
|
|
|
data = self._server.query(field.key)
|
|
|
|
return self.findItems(data, FilterChoice)
|
|
|
|
|
2021-03-11 21:04:04 +00:00
|
|
|
def _validateFilterField(self, field, values, libtype=None):
|
|
|
|
""" Validates a filter field and values are available as a custom filter for the library.
|
|
|
|
Returns the validated field and values as a URL param.
|
2021-03-11 07:48:11 +00:00
|
|
|
"""
|
2021-03-11 20:02:37 +00:00
|
|
|
match = re.match(r'([a-zA-Z\.]+)([!<>=&]*)', field)
|
2021-03-11 07:48:11 +00:00
|
|
|
if not match:
|
|
|
|
raise BadRequest('Invalid filter field: %s' % field)
|
|
|
|
field, operator = match.groups()
|
|
|
|
|
|
|
|
try:
|
|
|
|
filterField = next(f for f in self.listFields(libtype) if f.key.endswith(field))
|
|
|
|
except StopIteration:
|
2021-03-11 20:02:37 +00:00
|
|
|
raise BadRequest('Unknown filter field "%s" for libtype "%s"' % (field, libtype)) from None
|
2021-03-11 07:48:11 +00:00
|
|
|
|
2021-03-11 20:02:37 +00:00
|
|
|
field = filterField.key
|
2021-03-11 21:04:04 +00:00
|
|
|
operator = self._validateFieldOperator(filterField, operator)
|
|
|
|
result = self._validateFieldValue(filterField, values, libtype)
|
|
|
|
|
|
|
|
if operator.startswith('&'):
|
|
|
|
args = {field + operator[:-1]: result}
|
|
|
|
return urlencode(args, doseq=True)
|
|
|
|
else:
|
|
|
|
args = {field + operator[:-1]: ','.join(result)}
|
|
|
|
return urlencode(args)
|
|
|
|
|
|
|
|
def _validateFieldOperator(self, filterField, operator):
|
|
|
|
""" Validates filter operator is in the available operators.
|
|
|
|
Returns the validated operator.
|
|
|
|
"""
|
2021-03-11 07:48:11 +00:00
|
|
|
fieldType = self.getFieldType(filterField.type)
|
2021-03-11 20:22:44 +00:00
|
|
|
|
2021-03-11 20:02:37 +00:00
|
|
|
and_operator = False
|
|
|
|
if operator == '&':
|
|
|
|
and_operator = True
|
|
|
|
operator = ''
|
2021-03-11 07:48:11 +00:00
|
|
|
if fieldType.type == 'string' and operator in {'=', '!='}:
|
|
|
|
operator += '='
|
|
|
|
operator = (operator[:-1] if operator[-1:] == '=' else operator) + '='
|
|
|
|
|
|
|
|
try:
|
|
|
|
next(o for o in fieldType.operators if o.key == operator)
|
|
|
|
except StopIteration:
|
2021-03-11 21:04:04 +00:00
|
|
|
raise BadRequest('Invalid operator "%s" for filter field "%s"'
|
|
|
|
% (operator, filterField.key)) from None
|
|
|
|
|
|
|
|
return '&=' if and_operator else operator
|
2021-03-11 07:48:11 +00:00
|
|
|
|
2021-03-11 21:04:04 +00:00
|
|
|
def _validateFieldValue(self, filterField, values, libtype=None):
|
|
|
|
""" Validates filter values are the correct datatype and in the available filter choices.
|
|
|
|
Returns the validated list of values.
|
|
|
|
"""
|
2021-03-11 07:48:11 +00:00
|
|
|
if not isinstance(values, (list, tuple)):
|
|
|
|
values = [values]
|
|
|
|
|
2021-03-11 21:04:04 +00:00
|
|
|
fieldType = self.getFieldType(filterField.type)
|
2021-03-11 07:48:11 +00:00
|
|
|
choiceTypes = {'tag', 'subtitleLanguage', 'audioLanguage', 'resolution'}
|
2021-03-11 21:04:04 +00:00
|
|
|
if fieldType.type in choiceTypes:
|
|
|
|
filterChoices = self.listFilterChoices(filterField.key, libtype)
|
|
|
|
else:
|
|
|
|
filterChoices = []
|
2021-03-11 07:48:11 +00:00
|
|
|
|
2021-03-11 21:04:04 +00:00
|
|
|
results = []
|
2021-03-11 07:48:11 +00:00
|
|
|
|
2021-03-11 21:04:04 +00:00
|
|
|
try:
|
|
|
|
for value in values:
|
2021-03-11 07:48:11 +00:00
|
|
|
if fieldType.type == 'boolean':
|
|
|
|
value = int(utils.cast(bool, value))
|
|
|
|
elif fieldType.type == 'date':
|
|
|
|
value = int(utils.toDatetime(value, '%Y-%m-%d').timestamp())
|
|
|
|
elif fieldType.type == 'integer':
|
|
|
|
value = utils.cast(int, value)
|
|
|
|
elif fieldType.type == 'string':
|
|
|
|
value = str(value)
|
|
|
|
elif fieldType.type in choiceTypes:
|
2021-03-11 20:23:08 +00:00
|
|
|
value = str((value.id or value.tag) if isinstance(value, media.MediaTag) else value)
|
2021-03-11 21:04:04 +00:00
|
|
|
matchValue = value.lower()
|
|
|
|
value = next((f.key for f in filterChoices
|
|
|
|
if matchValue in {f.key.lower(), f.title.lower()}), value)
|
|
|
|
results.append(str(value))
|
|
|
|
except ValueError:
|
|
|
|
raise BadRequest('Invalid filter value "%s" for filter field "%s", value should be type %s'
|
|
|
|
% (value, filterField.key, fieldType.type)) from None
|
|
|
|
|
|
|
|
return results
|
2021-03-11 07:48:11 +00:00
|
|
|
|
2021-03-11 21:06:16 +00:00
|
|
|
def _validateSortField(self, sort, libtype=None):
|
|
|
|
""" Validates a filter sort field is available for the library.
|
|
|
|
Returns the validated sort field.
|
2021-03-11 07:48:11 +00:00
|
|
|
"""
|
2021-03-11 20:24:02 +00:00
|
|
|
match = re.match(r'([a-zA-Z\.]+):?([a-zA-Z]*)', sort)
|
2021-03-11 07:48:11 +00:00
|
|
|
if not match:
|
|
|
|
raise BadRequest('Invalid filter sort: %s' % sort)
|
|
|
|
sortField, sortDir = match.groups()
|
|
|
|
|
|
|
|
try:
|
|
|
|
filterSort = next(f for f in self.listSorts(libtype) if f.key == sortField)
|
|
|
|
except StopIteration:
|
2021-03-11 20:02:37 +00:00
|
|
|
raise BadRequest('Unknown sort field "%s" for libtype "%s"' % (sortField, libtype)) from None
|
2021-03-11 07:48:11 +00:00
|
|
|
|
|
|
|
if not sortDir:
|
|
|
|
sortDir = filterSort.defaultDirection
|
|
|
|
|
|
|
|
if sortDir not in {'asc', 'desc'}:
|
2021-03-11 20:02:37 +00:00
|
|
|
raise BadRequest('Invalid sort direction: %s, must be "asc" or "desc"' % sortDir)
|
2021-03-11 07:48:11 +00:00
|
|
|
|
|
|
|
return '%s:%s' % (sortField, sortDir)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2021-03-11 05:53:24 +00:00
|
|
|
def hubSearch(self, query, mediatype=None, limit=None):
|
|
|
|
""" Returns the hub search results for this library. See :func:`~plexapi.server.PlexServer.search`
|
|
|
|
for details and parameters.
|
|
|
|
"""
|
|
|
|
return self._server.search(query, mediatype, limit, sectionId=self.key)
|
|
|
|
|
2020-04-27 16:22:10 +00:00
|
|
|
def search(self, title=None, sort=None, maxresults=None,
|
|
|
|
libtype=None, container_start=0, container_size=X_PLEX_CONTAINER_SIZE, **kwargs):
|
|
|
|
""" Search the library. The http requests will be batched in container_size. If you're only looking for the first <num>
|
2016-03-31 20:52:48 +00:00
|
|
|
results, it would be wise to set the maxresults option to that amount so this functions
|
|
|
|
doesn't iterate over all results on the server.
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2017-01-26 04:21:13 +00:00
|
|
|
Parameters:
|
|
|
|
title (str): General string query to search for (optional).
|
|
|
|
sort (str): column:dir; column can be any of {addedAt, originallyAvailableAt, lastViewedAt,
|
|
|
|
titleSort, rating, mediaHeight, duration}. dir can be asc or desc (optional).
|
|
|
|
maxresults (int): Only return the specified number of results (optional).
|
2017-02-20 05:37:00 +00:00
|
|
|
libtype (str): Filter results to a spcifiec libtype (movie, show, episode, artist,
|
|
|
|
album, track; optional).
|
2020-04-27 16:22:10 +00:00
|
|
|
container_start (int): default 0
|
2020-04-26 21:29:28 +00:00
|
|
|
container_size (int): default X_PLEX_CONTAINER_SIZE in your config file.
|
2021-03-11 07:48:11 +00:00
|
|
|
**kwargs (dict): Any of the available custom filters for the current library section. Partial string
|
2018-09-14 18:03:23 +00:00
|
|
|
matches allowed. Multiple matches OR together. Negative filtering also possible, just add an
|
|
|
|
exclamation mark to the end of filter name, e.g. `resolution!=1x1`.
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2017-01-26 04:21:13 +00:00
|
|
|
* unwatched: Display or hide unwatched content (True, False). [all]
|
|
|
|
* duplicate: Display or hide duplicate items (True, False). [movie]
|
|
|
|
* actor: List of actors to search ([actor_or_id, ...]). [movie]
|
|
|
|
* collection: List of collections to search within ([collection_or_id, ...]). [all]
|
|
|
|
* contentRating: List of content ratings to search within ([rating_or_key, ...]). [movie,tv]
|
|
|
|
* country: List of countries to search within ([country_or_key, ...]). [movie,music]
|
|
|
|
* decade: List of decades to search within ([yyy0, ...]). [movie]
|
|
|
|
* director: List of directors to search ([director_or_id, ...]). [movie]
|
|
|
|
* genre: List Genres to search within ([genere_or_id, ...]). [all]
|
|
|
|
* network: List of TV networks to search within ([resolution_or_key, ...]). [tv]
|
|
|
|
* resolution: List of video resolutions to search within ([resolution_or_key, ...]). [movie]
|
|
|
|
* studio: List of studios to search within ([studio_or_key, ...]). [music]
|
|
|
|
* year: List of years to search within ([yyyy, ...]). [all]
|
2018-09-14 18:03:23 +00:00
|
|
|
|
|
|
|
Raises:
|
2021-03-11 07:48:11 +00:00
|
|
|
:exc:`~plexapi.exceptions.BadRequest`: When applying an unknown sort or filter.
|
2016-03-31 20:52:48 +00:00
|
|
|
"""
|
2021-03-11 20:02:37 +00:00
|
|
|
libtype = libtype or self.TYPE
|
2017-02-07 06:20:49 +00:00
|
|
|
# cleanup the core arguments
|
2016-03-31 20:52:48 +00:00
|
|
|
args = {}
|
2021-03-11 20:02:37 +00:00
|
|
|
filter_args = []
|
2021-03-11 07:48:11 +00:00
|
|
|
for field, values in list(kwargs.items()):
|
|
|
|
if field.split('__')[-1] not in OPERATORS:
|
2021-03-11 21:04:04 +00:00
|
|
|
filter_args.append(self._validateFilterField(field, values, libtype))
|
2021-03-11 07:48:11 +00:00
|
|
|
del kwargs[field]
|
2016-12-15 23:06:12 +00:00
|
|
|
if title is not None:
|
2020-05-11 21:12:15 +00:00
|
|
|
args['title'] = title
|
2016-12-15 23:06:12 +00:00
|
|
|
if sort is not None:
|
2021-03-11 21:06:16 +00:00
|
|
|
args['sort'] = self._validateSortField(sort, libtype)
|
2016-12-15 23:06:12 +00:00
|
|
|
if libtype is not None:
|
|
|
|
args['type'] = utils.searchType(libtype)
|
2020-04-26 20:51:57 +00:00
|
|
|
|
2020-04-26 20:34:13 +00:00
|
|
|
results = []
|
|
|
|
subresults = []
|
2020-04-29 11:42:19 +00:00
|
|
|
offset = container_start
|
2020-04-27 16:22:10 +00:00
|
|
|
|
|
|
|
if maxresults is not None:
|
|
|
|
container_size = min(container_size, maxresults)
|
2021-03-11 20:02:37 +00:00
|
|
|
|
|
|
|
key = '/library/sections/%s/all%s' % (self.key, utils.joinArgs(args))
|
|
|
|
if filter_args:
|
|
|
|
key += '&%s' % '&'.join(filter_args)
|
|
|
|
|
2020-04-26 20:34:13 +00:00
|
|
|
while True:
|
2020-04-27 16:22:10 +00:00
|
|
|
subresults = self.fetchItems(key, container_start=container_start,
|
2020-12-24 00:39:37 +00:00
|
|
|
container_size=container_size, **kwargs)
|
2020-04-29 11:42:19 +00:00
|
|
|
if not len(subresults):
|
|
|
|
if offset > self.totalSize:
|
|
|
|
log.info("container_start is higher then the number of items in the library")
|
|
|
|
break
|
|
|
|
|
2020-04-26 20:51:57 +00:00
|
|
|
results.extend(subresults)
|
|
|
|
|
2020-04-26 21:29:28 +00:00
|
|
|
# self.totalSize is not used as a condition in the while loop as
|
2020-04-26 20:51:57 +00:00
|
|
|
# this require a additional http request.
|
2020-04-26 21:29:28 +00:00
|
|
|
# self.totalSize is updated from .fetchItems
|
2020-04-29 11:42:19 +00:00
|
|
|
wanted_number_of_items = self.totalSize - offset
|
2020-04-27 16:22:10 +00:00
|
|
|
if maxresults is not None:
|
2020-04-29 11:42:19 +00:00
|
|
|
wanted_number_of_items = min(maxresults, wanted_number_of_items)
|
2020-04-27 16:22:10 +00:00
|
|
|
container_size = min(container_size, maxresults - len(results))
|
|
|
|
|
2020-04-29 11:42:19 +00:00
|
|
|
if wanted_number_of_items <= len(results):
|
2020-04-26 20:34:13 +00:00
|
|
|
break
|
|
|
|
|
2020-04-27 16:22:10 +00:00
|
|
|
container_start += container_size
|
2020-04-26 21:29:28 +00:00
|
|
|
|
2016-03-31 20:52:48 +00:00
|
|
|
return results
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2020-07-24 18:09:58 +00:00
|
|
|
def _locations(self):
|
|
|
|
""" Returns a list of :class:`~plexapi.library.Location` objects
|
|
|
|
"""
|
2020-12-24 04:39:15 +00:00
|
|
|
return self.findItems(self._data, Location)
|
2020-07-24 18:09:58 +00:00
|
|
|
|
2018-09-08 15:25:16 +00:00
|
|
|
def sync(self, policy, mediaSettings, client=None, clientId=None, title=None, sort=None, libtype=None,
|
|
|
|
**kwargs):
|
|
|
|
""" Add current library section as sync item for specified device.
|
2020-11-23 03:06:30 +00:00
|
|
|
See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting
|
|
|
|
and :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Parameters:
|
2020-11-23 03:06:30 +00:00
|
|
|
policy (:class:`~plexapi.sync.Policy`): policy of syncing the media (how many items to sync and process
|
2018-09-08 15:25:16 +00:00
|
|
|
watched media or not), generated automatically when method
|
|
|
|
called on specific LibrarySection object.
|
2020-11-23 03:06:30 +00:00
|
|
|
mediaSettings (:class:`~plexapi.sync.MediaSettings`): Transcoding settings used for the media, generated
|
2018-09-08 15:25:16 +00:00
|
|
|
automatically when method called on specific
|
|
|
|
LibrarySection object.
|
2020-11-23 03:06:30 +00:00
|
|
|
client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see
|
|
|
|
:func:`~plexapi.myplex.MyPlexAccount.sync`.
|
|
|
|
clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`.
|
|
|
|
title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be
|
2018-09-08 15:25:16 +00:00
|
|
|
generated from metadata of current media.
|
|
|
|
sort (str): formatted as `column:dir`; column can be any of {`addedAt`, `originallyAvailableAt`,
|
|
|
|
`lastViewedAt`, `titleSort`, `rating`, `mediaHeight`, `duration`}. dir can be `asc` or
|
|
|
|
`desc`.
|
|
|
|
libtype (str): Filter results to a specific libtype (`movie`, `show`, `episode`, `artist`, `album`,
|
|
|
|
`track`).
|
|
|
|
|
|
|
|
Returns:
|
2020-11-23 03:06:30 +00:00
|
|
|
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Raises:
|
2021-01-03 00:44:18 +00:00
|
|
|
:exc:`~plexapi.exceptions.BadRequest`: When the library is not allowed to sync.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
from plexapi import myplex
|
|
|
|
from plexapi.sync import Policy, MediaSettings, VIDEO_QUALITY_3_MBPS_720p
|
|
|
|
|
|
|
|
c = myplex.MyPlexAccount()
|
|
|
|
target = c.device('Plex Client')
|
|
|
|
sync_items_wd = c.syncItems(target.clientIdentifier)
|
|
|
|
srv = c.resource('Server Name').connect()
|
|
|
|
section = srv.library.section('Movies')
|
|
|
|
policy = Policy('count', unwatched=True, value=1)
|
|
|
|
media_settings = MediaSettings.create(VIDEO_QUALITY_3_MBPS_720p)
|
|
|
|
section.sync(target, policy, media_settings, title='Next best movie', sort='rating:desc')
|
|
|
|
|
|
|
|
"""
|
|
|
|
from plexapi.sync import SyncItem
|
|
|
|
|
|
|
|
if not self.allowSync:
|
|
|
|
raise BadRequest('The requested library is not allowed to sync')
|
|
|
|
|
|
|
|
args = {}
|
2021-03-11 20:02:37 +00:00
|
|
|
filter_args = []
|
2021-03-11 07:48:11 +00:00
|
|
|
for field, values in kwargs.items():
|
2021-03-11 21:04:04 +00:00
|
|
|
filter_args.append(self._validateFilterField(field, values, libtype))
|
2018-09-08 15:25:16 +00:00
|
|
|
if sort is not None:
|
2021-03-11 21:06:16 +00:00
|
|
|
args['sort'] = self._validateSortField(sort, libtype)
|
2018-09-08 15:25:16 +00:00
|
|
|
if libtype is not None:
|
|
|
|
args['type'] = utils.searchType(libtype)
|
|
|
|
|
|
|
|
myplex = self._server.myPlexAccount()
|
|
|
|
sync_item = SyncItem(self._server, None)
|
|
|
|
sync_item.title = title if title else self.title
|
|
|
|
sync_item.rootTitle = self.title
|
|
|
|
sync_item.contentType = self.CONTENT_TYPE
|
|
|
|
sync_item.metadataType = self.METADATA_TYPE
|
|
|
|
sync_item.machineIdentifier = self._server.machineIdentifier
|
|
|
|
|
2021-03-11 20:02:37 +00:00
|
|
|
key = '/library/sections/%s/all%s' % (self.key, utils.joinArgs(args))
|
|
|
|
if filter_args:
|
|
|
|
key += '&%s' % '&'.join(filter_args)
|
2018-09-08 15:25:16 +00:00
|
|
|
|
2021-03-11 20:02:37 +00:00
|
|
|
sync_item.location = 'library://%s/directory/%s' % (self.uuid, quote_plus(key))
|
2018-09-08 15:25:16 +00:00
|
|
|
sync_item.policy = policy
|
|
|
|
sync_item.mediaSettings = mediaSettings
|
|
|
|
|
|
|
|
return myplex.sync(client=client, clientId=clientId, sync_item=sync_item)
|
|
|
|
|
2019-11-20 11:50:25 +00:00
|
|
|
def history(self, maxresults=9999999, mindate=None):
|
|
|
|
""" Get Play History for this library Section for the owner.
|
|
|
|
Parameters:
|
|
|
|
maxresults (int): Only return the specified number of results (optional).
|
|
|
|
mindate (datetime): Min datetime to return results from.
|
|
|
|
"""
|
|
|
|
return self._server.history(maxresults=maxresults, mindate=mindate, librarySectionID=self.key, accountID=1)
|
2019-11-16 21:35:20 +00:00
|
|
|
|
2020-12-13 20:08:38 +00:00
|
|
|
@deprecated('use "collections" (plural) instead')
|
2020-12-04 20:27:45 +00:00
|
|
|
def collection(self, **kwargs):
|
|
|
|
return self.collections()
|
|
|
|
|
|
|
|
def collections(self, **kwargs):
|
|
|
|
""" Returns a list of collections from this library section.
|
2020-12-13 20:36:43 +00:00
|
|
|
See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting.
|
2020-12-04 20:27:45 +00:00
|
|
|
"""
|
|
|
|
return self.search(libtype='collection', **kwargs)
|
|
|
|
|
2020-12-04 20:28:11 +00:00
|
|
|
def playlists(self, **kwargs):
|
|
|
|
""" Returns a list of playlists from this library section. """
|
|
|
|
key = '/playlists?type=15&playlistType=%s§ionID=%s' % (self.CONTENT_TYPE, self.key)
|
|
|
|
return self.fetchItems(key, **kwargs)
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
class MovieSection(LibrarySection):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Represents a :class:`~plexapi.library.LibrarySection` section containing movies.
|
2017-01-31 00:02:22 +00:00
|
|
|
|
2017-01-26 04:21:13 +00:00
|
|
|
Attributes:
|
2017-02-13 02:55:55 +00:00
|
|
|
TAG (str): 'Directory'
|
2017-01-26 04:21:13 +00:00
|
|
|
TYPE (str): 'movie'
|
|
|
|
"""
|
2017-02-13 02:55:55 +00:00
|
|
|
TAG = 'Directory'
|
2014-12-29 03:21:58 +00:00
|
|
|
TYPE = 'movie'
|
2018-09-08 15:25:16 +00:00
|
|
|
METADATA_TYPE = 'movie'
|
|
|
|
CONTENT_TYPE = 'video'
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2021-03-11 07:56:43 +00:00
|
|
|
def searchMovies(self, **kwargs):
|
|
|
|
""" Search for a movie. See :func:`~plexapi.library.LibrarySection.search` for usage. """
|
|
|
|
return self.search(libtype='movie', **kwargs)
|
|
|
|
|
2018-09-08 15:25:16 +00:00
|
|
|
def sync(self, videoQuality, limit=None, unwatched=False, **kwargs):
|
|
|
|
""" Add current Movie library section as sync item for specified device.
|
2020-11-23 03:06:30 +00:00
|
|
|
See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and
|
|
|
|
:func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Parameters:
|
|
|
|
videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in
|
2020-11-23 03:06:30 +00:00
|
|
|
:mod:`~plexapi.sync` module.
|
2018-09-08 15:25:16 +00:00
|
|
|
limit (int): maximum count of movies to sync, unlimited if `None`.
|
|
|
|
unwatched (bool): if `True` watched videos wouldn't be synced.
|
|
|
|
|
|
|
|
Returns:
|
2020-11-23 03:06:30 +00:00
|
|
|
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
from plexapi import myplex
|
|
|
|
from plexapi.sync import VIDEO_QUALITY_3_MBPS_720p
|
|
|
|
|
|
|
|
c = myplex.MyPlexAccount()
|
|
|
|
target = c.device('Plex Client')
|
|
|
|
sync_items_wd = c.syncItems(target.clientIdentifier)
|
|
|
|
srv = c.resource('Server Name').connect()
|
|
|
|
section = srv.library.section('Movies')
|
|
|
|
section.sync(VIDEO_QUALITY_3_MBPS_720p, client=target, limit=1, unwatched=True,
|
|
|
|
title='Next best movie', sort='rating:desc')
|
|
|
|
|
|
|
|
"""
|
|
|
|
from plexapi.sync import Policy, MediaSettings
|
|
|
|
kwargs['mediaSettings'] = MediaSettings.createVideo(videoQuality)
|
|
|
|
kwargs['policy'] = Policy.create(limit, unwatched)
|
|
|
|
return super(MovieSection, self).sync(**kwargs)
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
class ShowSection(LibrarySection):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Represents a :class:`~plexapi.library.LibrarySection` section containing tv shows.
|
2017-01-31 00:02:22 +00:00
|
|
|
|
2017-01-26 04:21:13 +00:00
|
|
|
Attributes:
|
2017-02-13 02:55:55 +00:00
|
|
|
TAG (str): 'Directory'
|
2017-01-26 04:21:13 +00:00
|
|
|
TYPE (str): 'show'
|
|
|
|
"""
|
2020-06-30 13:39:16 +00:00
|
|
|
|
2017-02-13 02:55:55 +00:00
|
|
|
TAG = 'Directory'
|
2014-12-29 03:21:58 +00:00
|
|
|
TYPE = 'show'
|
2018-09-08 15:25:16 +00:00
|
|
|
METADATA_TYPE = 'episode'
|
|
|
|
CONTENT_TYPE = 'video'
|
2015-06-08 16:41:47 +00:00
|
|
|
|
2016-03-31 20:52:48 +00:00
|
|
|
def searchShows(self, **kwargs):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Search for a show. See :func:`~plexapi.library.LibrarySection.search` for usage. """
|
2016-03-31 20:52:48 +00:00
|
|
|
return self.search(libtype='show', **kwargs)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2021-03-11 07:56:54 +00:00
|
|
|
def searchSeasons(self, **kwargs):
|
|
|
|
""" Search for a season. See :func:`~plexapi.library.LibrarySection.search` for usage. """
|
|
|
|
return self.search(libtype='season', **kwargs)
|
|
|
|
|
2016-03-31 20:52:48 +00:00
|
|
|
def searchEpisodes(self, **kwargs):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Search for an episode. See :func:`~plexapi.library.LibrarySection.search` for usage. """
|
2016-03-31 20:52:48 +00:00
|
|
|
return self.search(libtype='episode', **kwargs)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2021-03-11 20:24:02 +00:00
|
|
|
def recentlyAdded(self, maxresults=50):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Returns a list of recently added episodes from this library section.
|
2017-01-31 00:02:22 +00:00
|
|
|
|
2017-01-26 04:21:13 +00:00
|
|
|
Parameters:
|
|
|
|
maxresults (int): Max number of items to return (default 50).
|
|
|
|
"""
|
2021-03-11 20:24:02 +00:00
|
|
|
return self.search(sort='episode.addedAt:desc', maxresults=maxresults)
|
2016-05-24 01:41:02 +00:00
|
|
|
|
2018-09-08 15:25:16 +00:00
|
|
|
def sync(self, videoQuality, limit=None, unwatched=False, **kwargs):
|
|
|
|
""" Add current Show library section as sync item for specified device.
|
2020-11-23 03:06:30 +00:00
|
|
|
See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and
|
|
|
|
:func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Parameters:
|
|
|
|
videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in
|
2020-11-23 03:06:30 +00:00
|
|
|
:mod:`~plexapi.sync` module.
|
2018-09-08 15:25:16 +00:00
|
|
|
limit (int): maximum count of episodes to sync, unlimited if `None`.
|
|
|
|
unwatched (bool): if `True` watched videos wouldn't be synced.
|
|
|
|
|
|
|
|
Returns:
|
2020-11-23 03:06:30 +00:00
|
|
|
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
from plexapi import myplex
|
|
|
|
from plexapi.sync import VIDEO_QUALITY_3_MBPS_720p
|
|
|
|
|
|
|
|
c = myplex.MyPlexAccount()
|
|
|
|
target = c.device('Plex Client')
|
|
|
|
sync_items_wd = c.syncItems(target.clientIdentifier)
|
|
|
|
srv = c.resource('Server Name').connect()
|
|
|
|
section = srv.library.section('TV-Shows')
|
|
|
|
section.sync(VIDEO_QUALITY_3_MBPS_720p, client=target, limit=1, unwatched=True,
|
|
|
|
title='Next unwatched episode')
|
|
|
|
|
|
|
|
"""
|
|
|
|
from plexapi.sync import Policy, MediaSettings
|
|
|
|
kwargs['mediaSettings'] = MediaSettings.createVideo(videoQuality)
|
|
|
|
kwargs['policy'] = Policy.create(limit, unwatched)
|
|
|
|
return super(ShowSection, self).sync(**kwargs)
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
2016-01-19 09:41:12 +00:00
|
|
|
class MusicSection(LibrarySection):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Represents a :class:`~plexapi.library.LibrarySection` section containing music artists.
|
2017-01-31 00:02:22 +00:00
|
|
|
|
2017-01-26 04:21:13 +00:00
|
|
|
Attributes:
|
2017-02-13 02:55:55 +00:00
|
|
|
TAG (str): 'Directory'
|
2017-01-26 04:21:13 +00:00
|
|
|
TYPE (str): 'artist'
|
|
|
|
"""
|
2017-02-13 02:55:55 +00:00
|
|
|
TAG = 'Directory'
|
2016-01-19 09:41:12 +00:00
|
|
|
TYPE = 'artist'
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2018-09-08 15:25:16 +00:00
|
|
|
CONTENT_TYPE = 'audio'
|
|
|
|
METADATA_TYPE = 'track'
|
|
|
|
|
2016-04-13 02:47:46 +00:00
|
|
|
def albums(self):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Returns a list of :class:`~plexapi.audio.Album` objects in this section. """
|
2017-02-07 06:20:49 +00:00
|
|
|
key = '/library/sections/%s/albums' % self.key
|
|
|
|
return self.fetchItems(key)
|
2016-04-13 02:47:46 +00:00
|
|
|
|
2020-06-22 19:03:28 +00:00
|
|
|
def stations(self):
|
|
|
|
""" Returns a list of :class:`~plexapi.audio.Album` objects in this section. """
|
|
|
|
key = '/hubs/sections/%s?includeStations=1' % self.key
|
|
|
|
return self.fetchItems(key, cls=Station)
|
|
|
|
|
2016-04-10 03:59:47 +00:00
|
|
|
def searchArtists(self, **kwargs):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Search for an artist. See :func:`~plexapi.library.LibrarySection.search` for usage. """
|
2016-03-31 20:52:48 +00:00
|
|
|
return self.search(libtype='artist', **kwargs)
|
2016-01-19 09:41:12 +00:00
|
|
|
|
2016-04-10 03:59:47 +00:00
|
|
|
def searchAlbums(self, **kwargs):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Search for an album. See :func:`~plexapi.library.LibrarySection.search` for usage. """
|
2016-03-31 20:52:48 +00:00
|
|
|
return self.search(libtype='album', **kwargs)
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2016-03-31 20:52:48 +00:00
|
|
|
def searchTracks(self, **kwargs):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Search for a track. See :func:`~plexapi.library.LibrarySection.search` for usage. """
|
2016-03-31 20:52:48 +00:00
|
|
|
return self.search(libtype='track', **kwargs)
|
2016-01-19 09:41:12 +00:00
|
|
|
|
2018-09-08 15:25:16 +00:00
|
|
|
def sync(self, bitrate, limit=None, **kwargs):
|
|
|
|
""" Add current Music library section as sync item for specified device.
|
2020-11-23 03:06:30 +00:00
|
|
|
See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and
|
|
|
|
:func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Parameters:
|
|
|
|
bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the
|
2020-11-23 03:06:30 +00:00
|
|
|
module :mod:`~plexapi.sync`.
|
2018-09-08 15:25:16 +00:00
|
|
|
limit (int): maximum count of tracks to sync, unlimited if `None`.
|
|
|
|
|
|
|
|
Returns:
|
2020-11-23 03:06:30 +00:00
|
|
|
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
from plexapi import myplex
|
|
|
|
from plexapi.sync import AUDIO_BITRATE_320_KBPS
|
|
|
|
|
|
|
|
c = myplex.MyPlexAccount()
|
|
|
|
target = c.device('Plex Client')
|
|
|
|
sync_items_wd = c.syncItems(target.clientIdentifier)
|
|
|
|
srv = c.resource('Server Name').connect()
|
|
|
|
section = srv.library.section('Music')
|
|
|
|
section.sync(AUDIO_BITRATE_320_KBPS, client=target, limit=100, sort='addedAt:desc',
|
|
|
|
title='New music')
|
|
|
|
|
|
|
|
"""
|
|
|
|
from plexapi.sync import Policy, MediaSettings
|
|
|
|
kwargs['mediaSettings'] = MediaSettings.createMusic(bitrate)
|
|
|
|
kwargs['policy'] = Policy.create(limit)
|
|
|
|
return super(MusicSection, self).sync(**kwargs)
|
|
|
|
|
2016-01-19 09:41:12 +00:00
|
|
|
|
2016-04-10 03:59:47 +00:00
|
|
|
class PhotoSection(LibrarySection):
|
2017-01-26 04:21:13 +00:00
|
|
|
""" Represents a :class:`~plexapi.library.LibrarySection` section containing photos.
|
2017-01-31 00:02:22 +00:00
|
|
|
|
2017-01-26 04:21:13 +00:00
|
|
|
Attributes:
|
2017-02-13 02:55:55 +00:00
|
|
|
TAG (str): 'Directory'
|
2017-01-26 04:21:13 +00:00
|
|
|
TYPE (str): 'photo'
|
|
|
|
"""
|
2017-02-13 02:55:55 +00:00
|
|
|
TAG = 'Directory'
|
2016-04-10 03:59:47 +00:00
|
|
|
TYPE = 'photo'
|
2018-09-08 15:25:16 +00:00
|
|
|
CONTENT_TYPE = 'photo'
|
|
|
|
METADATA_TYPE = 'photo'
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2020-12-27 04:53:55 +00:00
|
|
|
def all(self, libtype=None, **kwargs):
|
|
|
|
""" Returns a list of all items from this library section.
|
|
|
|
See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting.
|
|
|
|
"""
|
|
|
|
libtype = libtype or 'photoalbum'
|
|
|
|
return self.search(libtype=libtype, **kwargs)
|
|
|
|
|
2020-12-04 20:27:45 +00:00
|
|
|
def collections(self, **kwargs):
|
|
|
|
raise NotImplementedError('Collections are not available for a Photo library.')
|
|
|
|
|
2017-02-07 06:20:49 +00:00
|
|
|
def searchAlbums(self, title, **kwargs):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Search for an album. See :func:`~plexapi.library.LibrarySection.search` for usage. """
|
2018-09-14 18:03:23 +00:00
|
|
|
return self.search(libtype='photoalbum', title=title, **kwargs)
|
2016-12-15 23:06:12 +00:00
|
|
|
|
2017-01-31 00:02:22 +00:00
|
|
|
def searchPhotos(self, title, **kwargs):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Search for a photo. See :func:`~plexapi.library.LibrarySection.search` for usage. """
|
2018-09-14 18:03:23 +00:00
|
|
|
return self.search(libtype='photo', title=title, **kwargs)
|
2017-01-03 22:58:35 +00:00
|
|
|
|
2018-09-08 15:25:16 +00:00
|
|
|
def sync(self, resolution, limit=None, **kwargs):
|
|
|
|
""" Add current Music library section as sync item for specified device.
|
2020-11-23 03:06:30 +00:00
|
|
|
See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and
|
|
|
|
:func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Parameters:
|
|
|
|
resolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in the
|
2020-11-23 03:06:30 +00:00
|
|
|
module :mod:`~plexapi.sync`.
|
2018-09-08 15:25:16 +00:00
|
|
|
limit (int): maximum count of tracks to sync, unlimited if `None`.
|
|
|
|
|
|
|
|
Returns:
|
2020-11-23 03:06:30 +00:00
|
|
|
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
|
2018-09-08 15:25:16 +00:00
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
from plexapi import myplex
|
|
|
|
from plexapi.sync import PHOTO_QUALITY_HIGH
|
|
|
|
|
|
|
|
c = myplex.MyPlexAccount()
|
|
|
|
target = c.device('Plex Client')
|
|
|
|
sync_items_wd = c.syncItems(target.clientIdentifier)
|
|
|
|
srv = c.resource('Server Name').connect()
|
|
|
|
section = srv.library.section('Photos')
|
|
|
|
section.sync(PHOTO_QUALITY_HIGH, client=target, limit=100, sort='addedAt:desc',
|
|
|
|
title='Fresh photos')
|
|
|
|
|
|
|
|
"""
|
|
|
|
from plexapi.sync import Policy, MediaSettings
|
|
|
|
kwargs['mediaSettings'] = MediaSettings.createPhoto(resolution)
|
|
|
|
kwargs['policy'] = Policy.create(limit)
|
|
|
|
return super(PhotoSection, self).sync(**kwargs)
|
|
|
|
|
2017-01-03 22:58:35 +00:00
|
|
|
|
2020-09-21 21:06:14 +00:00
|
|
|
@utils.registerPlexObject
|
|
|
|
class LibraryTimeline(PlexObject):
|
|
|
|
"""Represents a LibrarySection timeline.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAG (str): 'LibraryTimeline'
|
|
|
|
size (int): Unknown
|
|
|
|
allowSync (bool): Unknown
|
|
|
|
art (str): Relative path to art image.
|
|
|
|
content (str): "secondary"
|
|
|
|
identifier (str): "com.plexapp.plugins.library"
|
|
|
|
latestEntryTime (int): Epoch timestamp
|
|
|
|
mediaTagPrefix (str): "/system/bundle/media/flags/"
|
|
|
|
mediaTagVersion (int): Unknown
|
|
|
|
thumb (str): Relative path to library thumb image.
|
|
|
|
title1 (str): Name of library section.
|
|
|
|
updateQueueSize (int): Number of items queued to update.
|
|
|
|
viewGroup (str): "secondary"
|
|
|
|
viewMode (int): Unknown
|
|
|
|
"""
|
|
|
|
TAG = 'LibraryTimeline'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self._data = data
|
|
|
|
self.size = utils.cast(int, data.attrib.get('size'))
|
|
|
|
self.allowSync = utils.cast(bool, data.attrib.get('allowSync'))
|
|
|
|
self.art = data.attrib.get('art')
|
|
|
|
self.content = data.attrib.get('content')
|
|
|
|
self.identifier = data.attrib.get('identifier')
|
|
|
|
self.latestEntryTime = utils.cast(int, data.attrib.get('latestEntryTime'))
|
|
|
|
self.mediaTagPrefix = data.attrib.get('mediaTagPrefix')
|
|
|
|
self.mediaTagVersion = utils.cast(int, data.attrib.get('mediaTagVersion'))
|
|
|
|
self.thumb = data.attrib.get('thumb')
|
|
|
|
self.title1 = data.attrib.get('title1')
|
|
|
|
self.updateQueueSize = utils.cast(int, data.attrib.get('updateQueueSize'))
|
|
|
|
self.viewGroup = data.attrib.get('viewGroup')
|
|
|
|
self.viewMode = utils.cast(int, data.attrib.get('viewMode'))
|
|
|
|
|
|
|
|
|
2020-06-09 19:56:21 +00:00
|
|
|
@utils.registerPlexObject
|
|
|
|
class Location(PlexObject):
|
|
|
|
""" Represents a single library Location.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAG (str): 'Location'
|
|
|
|
id (int): Location path ID.
|
|
|
|
path (str): Path used for library..
|
|
|
|
"""
|
|
|
|
TAG = 'Location'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self._data = data
|
|
|
|
self.id = utils.cast(int, data.attrib.get('id'))
|
|
|
|
self.path = data.attrib.get('path')
|
|
|
|
|
2017-02-07 06:20:49 +00:00
|
|
|
|
2017-02-13 02:55:55 +00:00
|
|
|
@utils.registerPlexObject
|
2017-02-07 06:20:49 +00:00
|
|
|
class Hub(PlexObject):
|
2017-02-14 04:32:27 +00:00
|
|
|
""" Represents a single Hub (or category) in the PlexServer search.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAG (str): 'Hub'
|
2021-01-24 20:29:44 +00:00
|
|
|
context (str): The context of the hub.
|
|
|
|
hubKey (str): API URL for these specific hub items.
|
|
|
|
hubIdentifier (str): The identifier of the hub.
|
|
|
|
key (str): API URL for the hub.
|
|
|
|
more (bool): True if there are more items to load (call reload() to fetch all items).
|
|
|
|
size (int): The number of items in the hub.
|
|
|
|
style (str): The style of the hub.
|
|
|
|
title (str): The title of the hub.
|
|
|
|
type (str): The type of items in the hub.
|
2017-02-14 04:32:27 +00:00
|
|
|
"""
|
2017-02-13 02:55:55 +00:00
|
|
|
TAG = 'Hub'
|
2017-02-07 06:20:49 +00:00
|
|
|
|
|
|
|
def _loadData(self, data):
|
2017-02-14 04:32:27 +00:00
|
|
|
""" Load attribute values from Plex XML response. """
|
2017-02-07 06:20:49 +00:00
|
|
|
self._data = data
|
2021-01-24 20:29:44 +00:00
|
|
|
self.context = data.attrib.get('context')
|
|
|
|
self.hubKey = data.attrib.get('hubKey')
|
2017-02-07 06:20:49 +00:00
|
|
|
self.hubIdentifier = data.attrib.get('hubIdentifier')
|
2021-01-24 20:29:44 +00:00
|
|
|
self.items = self.findItems(data)
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.more = utils.cast(bool, data.attrib.get('more'))
|
2017-02-07 06:20:49 +00:00
|
|
|
self.size = utils.cast(int, data.attrib.get('size'))
|
2021-01-24 20:29:44 +00:00
|
|
|
self.style = data.attrib.get('style')
|
2017-02-07 06:20:49 +00:00
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return self.size
|
2018-04-10 17:14:57 +00:00
|
|
|
|
2021-01-24 20:29:44 +00:00
|
|
|
def reload(self):
|
|
|
|
""" Reloads the hub to fetch all items in the hub. """
|
|
|
|
if self.more and self.key:
|
|
|
|
self.items = self.fetchItems(self.key)
|
|
|
|
self.more = False
|
|
|
|
self.size = len(self.items)
|
|
|
|
|
2018-04-10 17:24:39 +00:00
|
|
|
|
2021-01-24 20:48:38 +00:00
|
|
|
class HubMediaTag(PlexObject):
|
|
|
|
""" Base class of hub media tag search results.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
count (int): The number of items where this tag is found.
|
|
|
|
filter (str): The URL filter for the tag.
|
|
|
|
id (int): The id of the tag.
|
|
|
|
key (str): API URL (/library/section/<librarySectionID>/all?<filter>).
|
|
|
|
librarySectionID (int): The library section ID where the tag is found.
|
|
|
|
librarySectionKey (str): API URL for the library section (/library/section/<librarySectionID>)
|
|
|
|
librarySectionTitle (str): The library title where the tag is found.
|
|
|
|
librarySectionType (int): The library type where the tag is found.
|
|
|
|
reason (str): The reason for the search result.
|
|
|
|
reasonID (int): The reason ID for the search result.
|
|
|
|
reasonTitle (str): The reason title for the search result.
|
|
|
|
type (str): The type of search result (tag).
|
|
|
|
tag (str): The title of the tag.
|
|
|
|
tagType (int): The type ID of the tag.
|
|
|
|
tagValue (int): The value of the tag.
|
|
|
|
thumb (str): The URL for the thumbnail of the tag (if available).
|
|
|
|
"""
|
|
|
|
TAG = 'Directory'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self._data = data
|
|
|
|
self.count = utils.cast(int, data.attrib.get('count'))
|
|
|
|
self.filter = data.attrib.get('filter')
|
|
|
|
self.id = utils.cast(int, data.attrib.get('id'))
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.librarySectionID = utils.cast(int, data.attrib.get('librarySectionID'))
|
|
|
|
self.librarySectionKey = data.attrib.get('librarySectionKey')
|
|
|
|
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
|
|
|
|
self.librarySectionType = utils.cast(int, data.attrib.get('librarySectionType'))
|
|
|
|
self.reason = data.attrib.get('reason')
|
|
|
|
self.reasonID = utils.cast(int, data.attrib.get('reasonID'))
|
|
|
|
self.reasonTitle = data.attrib.get('reasonTitle')
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
self.tag = data.attrib.get('tag')
|
|
|
|
self.tagType = utils.cast(int, data.attrib.get('tagType'))
|
|
|
|
self.tagValue = utils.cast(int, data.attrib.get('tagValue'))
|
|
|
|
self.thumb = data.attrib.get('thumb')
|
|
|
|
|
|
|
|
|
|
|
|
@utils.registerPlexObject
|
|
|
|
class Tag(HubMediaTag):
|
|
|
|
""" Represents a single Tag hub search media tag.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAGTYPE (int): 0
|
|
|
|
"""
|
|
|
|
TAGTYPE = 0
|
|
|
|
|
|
|
|
|
|
|
|
@utils.registerPlexObject
|
|
|
|
class Genre(HubMediaTag):
|
|
|
|
""" Represents a single Genre hub search media tag.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAGTYPE (int): 1
|
|
|
|
"""
|
|
|
|
TAGTYPE = 1
|
|
|
|
|
|
|
|
|
|
|
|
@utils.registerPlexObject
|
|
|
|
class Director(HubMediaTag):
|
|
|
|
""" Represents a single Director hub search media tag.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAGTYPE (int): 4
|
|
|
|
"""
|
|
|
|
TAGTYPE = 4
|
|
|
|
|
|
|
|
|
|
|
|
@utils.registerPlexObject
|
|
|
|
class Actor(HubMediaTag):
|
|
|
|
""" Represents a single Actor hub search media tag.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAGTYPE (int): 6
|
|
|
|
"""
|
|
|
|
TAGTYPE = 6
|
|
|
|
|
|
|
|
|
|
|
|
@utils.registerPlexObject
|
|
|
|
class AutoTag(HubMediaTag):
|
|
|
|
""" Represents a single AutoTag hub search media tag.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAGTYPE (int): 207
|
|
|
|
"""
|
|
|
|
TAGTYPE = 207
|
|
|
|
|
|
|
|
|
|
|
|
@utils.registerPlexObject
|
|
|
|
class Place(HubMediaTag):
|
|
|
|
""" Represents a single Place hub search media tag.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAGTYPE (int): 400
|
|
|
|
"""
|
|
|
|
TAGTYPE = 400
|
|
|
|
|
2018-04-10 17:24:39 +00:00
|
|
|
|
2020-06-22 19:02:48 +00:00
|
|
|
@utils.registerPlexObject
|
|
|
|
class Station(PlexObject):
|
2020-06-22 19:27:45 +00:00
|
|
|
""" Represents the Station area in the MusicSection.
|
2020-06-22 19:02:48 +00:00
|
|
|
|
|
|
|
Attributes:
|
2020-06-22 19:27:45 +00:00
|
|
|
TITLE (str): 'Stations'
|
|
|
|
TYPE (str): 'station'
|
2020-06-22 19:02:48 +00:00
|
|
|
hubIdentifier (str): Unknown.
|
|
|
|
size (int): Number of items found.
|
|
|
|
title (str): Title of this Hub.
|
|
|
|
type (str): Type of items in the Hub.
|
2020-06-22 19:27:45 +00:00
|
|
|
more (str): Unknown.
|
|
|
|
style (str): Unknown
|
2020-06-22 19:02:48 +00:00
|
|
|
items (str): List of items in the Hub.
|
|
|
|
"""
|
|
|
|
TITLE = 'Stations'
|
|
|
|
TYPE = 'station'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self._data = data
|
|
|
|
self.hubIdentifier = data.attrib.get('hubIdentifier')
|
|
|
|
self.size = utils.cast(int, data.attrib.get('size'))
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
self.more = data.attrib.get('more')
|
|
|
|
self.style = data.attrib.get('style')
|
|
|
|
self.items = self.findItems(data)
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return self.size
|
|
|
|
|
|
|
|
|
2021-03-11 07:45:29 +00:00
|
|
|
class FilteringType(PlexObject):
|
|
|
|
""" Represents a single filtering Type object for a library.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAG (str): 'Type'
|
|
|
|
active (bool): True if this filter type is currently active.
|
|
|
|
fields (List<:class:`~plexapi.library.FilteringField`>): List of field objects.
|
|
|
|
filters (List<:class:`~plexapi.library.FilteringFilter`>): List of filter objects.
|
|
|
|
key (str): The API URL path for the libtype filter.
|
|
|
|
sorts (List<:class:`~plexapi.library.FilteringSort`>): List of sort objects.
|
|
|
|
title (str): The title for the libtype filter.
|
|
|
|
type (str): The libtype for the filter.
|
|
|
|
"""
|
|
|
|
TAG = 'Type'
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
_type = self._clean(self.firstAttr('type'))
|
|
|
|
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, _type] if p])
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
self._data = data
|
|
|
|
self.active = utils.cast(bool, data.attrib.get('active', '0'))
|
|
|
|
self.fields = self.findItems(data, FilteringField)
|
|
|
|
self.filters = self.findItems(data, FilteringFilter)
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.sorts = self.findItems(data, FilteringSort)
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
|
|
|
|
|
|
|
|
class FilteringFilter(PlexObject):
|
|
|
|
""" Represents a single Filter object for a :class:`~plexapi.library.FilteringType`.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAG (str): 'Filter'
|
|
|
|
filter (str): The key for the filter.
|
|
|
|
filterType (str): The :class:`~plexapi.library.FilteringFieldType` type (string, boolean, integer, date, etc).
|
|
|
|
key (str): The API URL path for the filter.
|
|
|
|
title (str): The title of the filter.
|
|
|
|
type (str): 'filter'
|
|
|
|
"""
|
|
|
|
TAG = 'Filter'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
self._data = data
|
|
|
|
self.filter = data.attrib.get('filter')
|
|
|
|
self.filterType = data.attrib.get('filterType')
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
|
|
|
|
|
|
|
|
class FilteringSort(PlexObject):
|
|
|
|
""" Represents a single Sort object for a :class:`~plexapi.library.FilteringType`.
|
2020-06-30 13:06:26 +00:00
|
|
|
|
2020-07-02 04:40:12 +00:00
|
|
|
Attributes:
|
|
|
|
TAG (str): 'Sort'
|
2021-03-11 07:45:29 +00:00
|
|
|
defaultDirection (str): The default sorting direction.
|
|
|
|
descKey (str): The URL key for sorting with desc.
|
|
|
|
firstCharacterKey (str): API URL path for first character endpoint.
|
|
|
|
key (str): The URL key for the sorting.
|
|
|
|
title (str): The title of the sorting.
|
2020-06-30 13:06:26 +00:00
|
|
|
"""
|
|
|
|
TAG = 'Sort'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self._data = data
|
|
|
|
self.defaultDirection = data.attrib.get('defaultDirection')
|
|
|
|
self.descKey = data.attrib.get('descKey')
|
2021-03-11 07:45:29 +00:00
|
|
|
self.firstCharacterKey = data.attrib.get('firstCharacterKey')
|
2020-06-30 13:06:26 +00:00
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
|
|
|
|
|
2021-03-11 07:45:29 +00:00
|
|
|
class FilteringField(PlexObject):
|
|
|
|
""" Represents a single Field object for a :class:`~plexapi.library.FilteringType`.
|
2020-06-30 13:23:18 +00:00
|
|
|
|
2020-07-02 04:40:51 +00:00
|
|
|
Attributes:
|
|
|
|
TAG (str): 'Field'
|
2021-03-11 07:45:29 +00:00
|
|
|
key (str): The URL key for the filter field.
|
|
|
|
title (str): The title of the filter field.
|
|
|
|
type (str): The :class:`~plexapi.library.FilteringFieldType` type (string, boolean, integer, date, etc).
|
|
|
|
subType (str): The subtype of the filter (decade, rating, etc).
|
2020-06-30 13:23:18 +00:00
|
|
|
"""
|
|
|
|
TAG = 'Field'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self._data = data
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
self.subType = data.attrib.get('subType')
|
|
|
|
|
|
|
|
|
2021-03-11 07:45:29 +00:00
|
|
|
class FilteringFieldType(PlexObject):
|
|
|
|
""" Represents a single FieldType for library filtering.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAG (str): 'FieldType'
|
|
|
|
type (str): The filtering data type (string, boolean, integer, date, etc).
|
|
|
|
operators (List<:class:`~plexapi.library.FilteringOperator`>): List of operator objects.
|
|
|
|
"""
|
|
|
|
TAG = 'FieldType'
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
_type = self._clean(self.firstAttr('type'))
|
|
|
|
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, _type] if p])
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self._data = data
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
self.operators = self.findItems(data, FilteringOperator)
|
|
|
|
|
|
|
|
|
|
|
|
class FilteringOperator(PlexObject):
|
|
|
|
""" Represents an single Operator for a :class:`~plexapi.library.FilteringFieldType`.
|
2020-06-30 13:23:18 +00:00
|
|
|
|
2020-07-02 04:42:28 +00:00
|
|
|
Attributes:
|
|
|
|
TAG (str): 'Operator'
|
2021-03-11 07:45:29 +00:00
|
|
|
key (str): The URL key for the operator.
|
|
|
|
title (str): The title of the operator.
|
2020-06-30 13:23:18 +00:00
|
|
|
"""
|
|
|
|
TAG = 'Operator'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
|
|
|
|
|
2021-03-11 07:45:29 +00:00
|
|
|
class FilterChoice(PlexObject):
|
|
|
|
""" Represents a single FilterChoice object.
|
|
|
|
These objects are gathered when using filters while searching for library items and is the
|
|
|
|
object returned in the result set of :func:`~plexapi.library.LibrarySection.listFilterChoices`.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAG (str): 'Directory'
|
|
|
|
fastKey (str): API URL path to quickly list all items with this filter choice.
|
|
|
|
(/library/sections/<section>/all?genre=<key>)
|
|
|
|
key (str): The id value of this filter choice.
|
|
|
|
thumb (str): Thumbnail URL for the filter choice.
|
|
|
|
title (str): The title of the filter choice.
|
|
|
|
type (str): The filter type (genre, contentRating, etc).
|
|
|
|
"""
|
|
|
|
TAG = 'Directory'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self._data = data
|
|
|
|
self.fastKey = data.attrib.get('fastKey')
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.thumb = data.attrib.get('thumb')
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
|
|
|
|
|
2020-07-02 05:33:18 +00:00
|
|
|
class Folder(PlexObject):
|
|
|
|
""" Represents a Folder inside a library.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
key (str): Url key for folder.
|
|
|
|
title (str): Title of folder.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
|
|
|
|
def subfolders(self):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Returns a list of available :class:`~plexapi.library.Folder` for this folder.
|
2020-07-02 05:33:18 +00:00
|
|
|
Continue down subfolders until a mediaType is found.
|
|
|
|
"""
|
|
|
|
if self.key.startswith('/library/metadata'):
|
|
|
|
return self.fetchItems(self.key)
|
|
|
|
else:
|
|
|
|
return self.fetchItems(self.key, Folder)
|
|
|
|
|
2020-07-30 14:33:58 +00:00
|
|
|
def allSubfolders(self):
|
2020-11-23 03:06:30 +00:00
|
|
|
""" Returns a list of all available :class:`~plexapi.library.Folder` for this folder.
|
|
|
|
Only returns :class:`~plexapi.library.Folder`.
|
2020-07-30 14:33:58 +00:00
|
|
|
"""
|
2020-09-09 19:23:56 +00:00
|
|
|
folders = []
|
2020-07-30 14:33:58 +00:00
|
|
|
for folder in self.subfolders():
|
|
|
|
if not folder.key.startswith('/library/metadata'):
|
|
|
|
folders.append(folder)
|
|
|
|
while True:
|
|
|
|
for subfolder in folder.subfolders():
|
|
|
|
if not subfolder.key.startswith('/library/metadata'):
|
|
|
|
folders.append(subfolder)
|
|
|
|
continue
|
|
|
|
break
|
|
|
|
return folders
|
|
|
|
|
2020-07-02 05:33:18 +00:00
|
|
|
|
2020-06-30 12:59:30 +00:00
|
|
|
class FirstCharacter(PlexObject):
|
2020-07-02 04:43:21 +00:00
|
|
|
""" Represents a First Character element from a library.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
key (str): Url key for character.
|
|
|
|
size (str): Total amount of library items starting with this character.
|
|
|
|
title (str): Character (#, !, A, B, C, ...).
|
|
|
|
"""
|
2020-06-30 12:59:30 +00:00
|
|
|
def _loadData(self, data):
|
|
|
|
""" Load attribute values from Plex XML response. """
|
|
|
|
self._data = data
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.size = data.attrib.get('size')
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
|
|
|
|
|
2020-11-16 01:54:48 +00:00
|
|
|
@utils.registerPlexObject
|
|
|
|
class Path(PlexObject):
|
|
|
|
""" Represents a single directory Path.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAG (str): 'Path'
|
|
|
|
|
|
|
|
home (bool): True if the path is the home directory
|
|
|
|
key (str): API URL (/services/browse/<base64path>)
|
|
|
|
network (bool): True if path is a network location
|
2020-11-16 05:28:58 +00:00
|
|
|
path (str): Full path to folder
|
|
|
|
title (str): Folder name
|
2020-11-16 01:54:48 +00:00
|
|
|
"""
|
|
|
|
TAG = 'Path'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
self.home = utils.cast(bool, data.attrib.get('home'))
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.network = utils.cast(bool, data.attrib.get('network'))
|
|
|
|
self.path = data.attrib.get('path')
|
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
|
2020-11-16 05:21:08 +00:00
|
|
|
def browse(self, includeFiles=True):
|
2020-11-23 03:04:14 +00:00
|
|
|
""" Alias for :func:`~plexapi.server.PlexServer.browse`. """
|
2020-11-16 05:21:08 +00:00
|
|
|
return self._server.browse(self, includeFiles)
|
2020-11-16 01:54:48 +00:00
|
|
|
|
2020-11-16 05:10:13 +00:00
|
|
|
def walk(self):
|
2020-11-23 03:04:14 +00:00
|
|
|
""" Alias for :func:`~plexapi.server.PlexServer.walk`. """
|
2020-11-16 05:10:13 +00:00
|
|
|
for path, paths, files in self._server.walk(self):
|
|
|
|
yield path, paths, files
|
2020-11-16 01:54:48 +00:00
|
|
|
|
2020-11-16 05:21:08 +00:00
|
|
|
|
2020-11-16 01:54:48 +00:00
|
|
|
@utils.registerPlexObject
|
|
|
|
class File(PlexObject):
|
|
|
|
""" Represents a single File.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
TAG (str): 'File'
|
|
|
|
|
|
|
|
key (str): API URL (/services/browse/<base64path>)
|
2020-11-16 05:28:58 +00:00
|
|
|
path (str): Full path to file
|
|
|
|
title (str): File name
|
2020-11-16 01:54:48 +00:00
|
|
|
"""
|
|
|
|
TAG = 'File'
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
self.path = data.attrib.get('path')
|
|
|
|
self.title = data.attrib.get('title')
|