diff --git a/plexapi/audio.py b/plexapi/audio.py
index 10ba97689..a7693e2fd 100644
--- a/plexapi/audio.py
+++ b/plexapi/audio.py
@@ -6,13 +6,14 @@
from typing import Any, Dict, List, Optional, TypeVar
from plexapi import media, utils
-from plexapi.base import Playable, PlexPartialObject, PlexHistory, PlexSession
+from plexapi.base import PlexPartialObject, PlexHistory, PlexSession
from plexapi.exceptions import BadRequest
from plexapi.mixins import (
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, PlayedUnplayedMixin, RatingMixin,
ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, ThemeMixin, ThemeUrlMixin,
ArtistEditMixins, AlbumEditMixins, TrackEditMixins
)
+from plexapi.playable import Playable
from plexapi.playlist import Playlist
@@ -472,7 +473,6 @@ def _loadData(self, data):
self.grandparentTitle = data.attrib.get('grandparentTitle')
self.guids = self.findItems(data, media.Guid)
self.labels = self.findItems(data, media.Label)
- self.media = self.findItems(data, media.Media)
self.originalTitle = data.attrib.get('originalTitle')
self.parentGuid = data.attrib.get('parentGuid')
self.parentIndex = utils.cast(int, data.attrib.get('parentIndex'))
diff --git a/plexapi/base.py b/plexapi/base.py
index 4e6d59d8b..fcae1903c 100644
--- a/plexapi/base.py
+++ b/plexapi/base.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import re
+from typing import Optional, TypeVar
import weakref
from functools import cached_property
from urllib.parse import urlencode
@@ -8,6 +9,8 @@
from plexapi import CONFIG, X_PLEX_CONTAINER_SIZE, log, utils
from plexapi.exceptions import BadRequest, NotFound, UnknownType, Unsupported
+T = TypeVar('T')
+
USER_DONT_RELOAD_FOR_KEYS = set()
_DONT_RELOAD_FOR_KEYS = {'key'}
OPERATORS = {
@@ -40,9 +43,10 @@ class PlexObject:
initpath (str): Relative path requested when retrieving specified `data` (optional).
parent (:class:`~plexapi.base.PlexObject`): The parent object that this object is built from (optional).
"""
- TAG = None # xml element tag
- TYPE = None # xml element type
- key = None # plex relative url
+ TAG: Optional[str] = None # xml element tag
+ TYPE: Optional[str] = None # xml element type
+ key: Optional[str] = None # plex relative url
+ ratingKey: Optional[int] = None
def __init__(self, server, data, initpath=None, parent=None):
self._server = server
@@ -82,7 +86,7 @@ def _clean(self, value):
value = value.replace('/devices/', '')
return value.replace(' ', '-')[:20]
- def _buildItem(self, elem, cls=None, initpath=None):
+ def _buildItem(self, elem, cls: Optional[T] = None, initpath=None):
""" Factory function to build objects based on registered PLEXOBJECTS. """
# cls is specified, build the object and return
initpath = initpath or self._initpath
@@ -309,7 +313,7 @@ def fetchItem(self, ekey, cls=None, **kwargs):
clsname = cls.__name__ if cls else 'None'
raise NotFound(f'Unable to find elem: cls={clsname}, attrs={kwargs}') from None
- def findItems(self, data, cls=None, initpath=None, rtag=None, **kwargs):
+ def findItems(self, data, cls: Optional[T] = None, initpath=None, rtag=None, **kwargs):
""" Load the specified data 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.
@@ -709,158 +713,6 @@ def playQueue(self, *args, **kwargs):
return PlayQueue.create(self._server, self, *args, **kwargs)
-class Playable:
- """ This is a general place to store functions specific to media that is Playable.
- Things were getting mixed up a bit when dealing with Shows, Season, Artists,
- Albums which are all not playable.
-
- Attributes:
- playlistItemID (int): Playlist item ID (only populated for :class:`~plexapi.playlist.Playlist` items).
- playQueueItemID (int): PlayQueue item ID (only populated for :class:`~plexapi.playlist.PlayQueue` items).
- """
-
- def _loadData(self, data):
- self.playlistItemID = utils.cast(int, data.attrib.get('playlistItemID')) # playlist
- self.playQueueItemID = utils.cast(int, data.attrib.get('playQueueItemID')) # playqueue
-
- def getStreamURL(self, **kwargs):
- """ Returns a stream url that may be used by external applications such as VLC.
-
- Parameters:
- **kwargs (dict): optional parameters to manipulate the playback when accessing
- the stream. A few known parameters include: maxVideoBitrate, videoResolution
- offset, copyts, protocol, mediaIndex, partIndex, platform.
-
- Raises:
- :exc:`~plexapi.exceptions.Unsupported`: When the item doesn't support fetching a stream URL.
- """
- if self.TYPE not in ('movie', 'episode', 'track', 'clip'):
- raise Unsupported(f'Fetching stream URL for {self.TYPE} is unsupported.')
-
- mvb = kwargs.pop('maxVideoBitrate', None)
- vr = kwargs.pop('videoResolution', '')
- protocol = kwargs.pop('protocol', None)
-
- params = {
- 'path': self.key,
- 'mediaIndex': kwargs.pop('mediaIndex', 0),
- 'partIndex': kwargs.pop('mediaIndex', 0),
- 'protocol': protocol,
- 'fastSeek': kwargs.pop('fastSeek', 1),
- 'copyts': kwargs.pop('copyts', 1),
- 'offset': kwargs.pop('offset', 0),
- 'maxVideoBitrate': max(mvb, 64) if mvb else None,
- 'videoResolution': vr if re.match(r'^\d+x\d+$', vr) else None,
- 'X-Plex-Platform': kwargs.pop('platform', 'Chrome')
- }
- params.update(kwargs)
-
- # remove None values
- params = {k: v for k, v in params.items() if v is not None}
- streamtype = 'audio' if self.TYPE in ('track', 'album') else 'video'
- ext = 'mpd' if protocol == 'dash' else 'm3u8'
-
- return self._server.url(
- f'/{streamtype}/:/transcode/universal/start.{ext}?{urlencode(params)}',
- includeToken=True
- )
-
- def iterParts(self):
- """ Iterates over the parts of this media item. """
- for item in self.media:
- for part in item.parts:
- yield part
-
- def play(self, client):
- """ Start playback on the specified client.
-
- Parameters:
- client (:class:`~plexapi.client.PlexClient`): Client to start playing on.
- """
- client.playMedia(self)
-
- def download(self, savepath=None, keep_original_name=False, **kwargs):
- """ Downloads the media item to the specified location. Returns a list of
- filepaths that have been saved to disk.
-
- Parameters:
- savepath (str): Defaults to current working dir.
- keep_original_name (bool): True to keep the original filename otherwise
- a friendlier filename is generated. See filenames below.
- **kwargs (dict): Additional options passed into :func:`~plexapi.audio.Track.getStreamURL`
- to download a transcoded stream, otherwise the media item will be downloaded
- as-is and saved to disk.
-
- **Filenames**
-
- * Movie: ``
()``
- * Episode: `` - s00e00 - ``
- * Track: `` - - 00 -