Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions plexapi/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from plexapi import library, media, utils
from plexapi.base import Playable, PlexPartialObject
from plexapi.exceptions import BadRequest
from plexapi.mixins import SplitMergeMixin, UnmatchMatchMixin
from plexapi.mixins import ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin
from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin


Expand Down Expand Up @@ -125,7 +125,7 @@ def sync(self, bitrate, client=None, clientId=None, limit=None, title=None):


@utils.registerPlexObject
class Artist(Audio, SplitMergeMixin, UnmatchMatchMixin,
class Artist(Audio, ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin,
CollectionMixin, CountryMixin, GenreMixin, MoodMixin, SimilarArtistMixin, StyleMixin):
""" Represents a single Artist.

Expand Down Expand Up @@ -229,7 +229,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs):


@utils.registerPlexObject
class Album(Audio, UnmatchMatchMixin,
class Album(Audio, ArtMixin, PosterMixin, UnmatchMatchMixin,
CollectionMixin, GenreMixin, LabelMixin, MoodMixin, StyleMixin):
""" Represents a single Album.

Expand Down
47 changes: 0 additions & 47 deletions plexapi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,53 +513,6 @@ def history(self, maxresults=9999999, mindate=None):
"""
return self._server.history(maxresults=maxresults, mindate=mindate, ratingKey=self.ratingKey)

def posters(self):
""" Returns list of available poster objects. :class:`~plexapi.media.Poster`. """

return self.fetchItems('%s/posters' % self.key)

def uploadPoster(self, url=None, filepath=None):
""" Upload poster from url or filepath. :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video`. """
if url:
key = '%s/posters?url=%s' % (self.key, quote_plus(url))
self._server.query(key, method=self._server._session.post)
elif filepath:
key = '%s/posters?' % self.key
data = open(filepath, 'rb').read()
self._server.query(key, method=self._server._session.post, data=data)

def setPoster(self, poster):
""" Set . :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video` """
poster.select()

def arts(self):
""" Returns list of available art objects. :class:`~plexapi.media.Poster`. """

return self.fetchItems('%s/arts' % self.key)

def uploadArt(self, url=None, filepath=None):
""" Upload art from url or filepath. :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video`. """
if url:
key = '/library/metadata/%s/arts?url=%s' % (self.ratingKey, quote_plus(url))
self._server.query(key, method=self._server._session.post)
elif filepath:
key = '/library/metadata/%s/arts?' % self.ratingKey
data = open(filepath, 'rb').read()
self._server.query(key, method=self._server._session.post, data=data)

def setArt(self, art):
""" Set :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video` """
art.select()

# The photo tag cant be built atm. TODO
# def arts(self):
# part = '%s/arts' % self.key
# return self.fetchItem(part)

# def poster(self):
# part = '%s/posters' % self.key
# return self.fetchItem(part, etag='Photo')


class Playable(object):
""" This is a general place to store functions specific to media that is Playable.
Expand Down
41 changes: 2 additions & 39 deletions plexapi/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from plexapi import X_PLEX_CONTAINER_SIZE, log, media, utils
from plexapi.base import OPERATORS, PlexObject, PlexPartialObject
from plexapi.exceptions import BadRequest, NotFound
from plexapi.mixins import ArtMixin, PosterMixin
from plexapi.mixins import LabelMixin
from plexapi.settings import Setting
from plexapi.utils import deprecated
Expand Down Expand Up @@ -1527,7 +1528,7 @@ def _loadData(self, data):


@utils.registerPlexObject
class Collections(PlexPartialObject, LabelMixin):
class Collections(PlexPartialObject, ArtMixin, PosterMixin, LabelMixin):
""" Represents a single Collection.

Attributes:
Expand Down Expand Up @@ -1684,44 +1685,6 @@ def sortUpdate(self, sort=None):
part = '/library/metadata/%s/prefs?collectionSort=%s' % (self.ratingKey, key)
return self._server.query(part, method=self._server._session.put)

def posters(self):
""" Returns list of available poster objects. :class:`~plexapi.media.Poster`. """

return self.fetchItems('/library/metadata/%s/posters' % self.ratingKey)

def uploadPoster(self, url=None, filepath=None):
""" Upload poster from url or filepath. :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video`. """
if url:
key = '/library/metadata/%s/posters?url=%s' % (self.ratingKey, quote_plus(url))
self._server.query(key, method=self._server._session.post)
elif filepath:
key = '/library/metadata/%s/posters?' % self.ratingKey
data = open(filepath, 'rb').read()
self._server.query(key, method=self._server._session.post, data=data)

def setPoster(self, poster):
""" Set . :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video` """
poster.select()

def arts(self):
""" Returns list of available art objects. :class:`~plexapi.media.Poster`. """

return self.fetchItems('/library/metadata/%s/arts' % self.ratingKey)

def uploadArt(self, url=None, filepath=None):
""" Upload art from url or filepath. :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video`. """
if url:
key = '/library/metadata/%s/arts?url=%s' % (self.ratingKey, quote_plus(url))
self._server.query(key, method=self._server._session.post)
elif filepath:
key = '/library/metadata/%s/arts?' % self.ratingKey
data = open(filepath, 'rb').read()
self._server.query(key, method=self._server._session.post, data=data)

def setArt(self, art):
""" Set :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video` """
art.select()

# def edit(self, **kwargs):
# TODO

Expand Down
21 changes: 17 additions & 4 deletions plexapi/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -807,20 +807,25 @@ class Style(MediaTag):
FILTER = 'style'


@utils.registerPlexObject
class Poster(PlexObject):
""" Represents a Poster.
class BasePosterArt(PlexObject):
""" Base class for all Poster and Art objects.

Attributes:
TAG (str): 'Photo'
key (str): API URL (/library/metadata/<ratingkey>).
provider (str): The source of the poster or art.
ratingKey (str): Unique key identifying the poster or art.
selected (bool): True if the poster or art is currently selected.
thumb (str): The URL to retrieve the poster or art thumbnail.
"""
TAG = 'Photo'

def _loadData(self, data):
self._data = data
self.key = data.attrib.get('key')
self.provider = data.attrib.get('provider')
self.ratingKey = data.attrib.get('ratingKey')
self.selected = data.attrib.get('selected')
self.selected = cast(bool, data.attrib.get('selected'))
self.thumb = data.attrib.get('thumb')

def select(self):
Expand All @@ -832,6 +837,14 @@ def select(self):
pass


class Poster(BasePosterArt):
""" Represents a single Poster object. """


class Art(BasePosterArt):
""" Represents a single Art object. """


@utils.registerPlexObject
class Producer(MediaTag):
""" Represents a single Producer media tag.
Expand Down
68 changes: 65 additions & 3 deletions plexapi/mixins.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,72 @@
# -*- coding: utf-8 -*-
from urllib.parse import urlencode
from urllib.parse import quote_plus, urlencode

from plexapi import utils
from plexapi import media, utils
from plexapi.exceptions import NotFound


class ArtMixin(object):
""" Mixin for Plex objects that can have artwork."""

def arts(self):
""" Returns list of available :class:`~plexapi.media.Art` objects. """
return self.fetchItems('/library/metadata/%s/arts' % self.ratingKey, cls=media.Art)

def uploadArt(self, url=None, filepath=None):
""" Upload art from url or filepath and set it as the selected art.

Parameters:
url (str): The full URL to the image to upload.
filepath (str): The full file path the the image to upload.
"""
if url:
key = '/library/metadata/%s/arts?url=%s' % (self.ratingKey, quote_plus(url))
self._server.query(key, method=self._server._session.post)
elif filepath:
key = '/library/metadata/%s/arts?' % self.ratingKey
data = open(filepath, 'rb').read()
self._server.query(key, method=self._server._session.post, data=data)

def setArt(self, art):
""" Set the artwork for a Plex object.

Parameters:
art (:class:`~plexapi.media.Art`): The art object to select.
"""
art.select()


class PosterMixin(object):
""" Mixin for Plex objects that can have posters."""

def posters(self):
""" Returns list of available :class:`~plexapi.media.Poster` objects. """
return self.fetchItems('/library/metadata/%s/posters' % self.ratingKey, cls=media.Poster)

def uploadPoster(self, url=None, filepath=None):
""" Upload poster from url or filepath and set it as the selected poster.

Parameters:
url (str): The full URL to the image to upload.
filepath (str): The full file path the the image to upload.
"""
if url:
key = '/library/metadata/%s/posters?url=%s' % (self.ratingKey, quote_plus(url))
self._server.query(key, method=self._server._session.post)
elif filepath:
key = '/library/metadata/%s/posters?' % self.ratingKey
data = open(filepath, 'rb').read()
self._server.query(key, method=self._server._session.post, data=data)

def setPoster(self, poster):
""" Set the poster for a Plex object.

Parameters:
poster (:class:`~plexapi.media.Poster`): The poster object to select.
"""
poster.select()


class SplitMergeMixin(object):
""" Mixin for Plex objects that can be split and merged."""

Expand Down Expand Up @@ -319,7 +381,7 @@ def removeTag(self, tags):
self._edit_tags('tag', tags, remove=True)


class EditWriter(object):
class WriterMixin(object):
""" Mixin for Plex objects that can have writers. """

def addWriter(self, writers):
Expand Down
41 changes: 2 additions & 39 deletions plexapi/playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
from plexapi.base import Playable, PlexPartialObject
from plexapi.exceptions import BadRequest, NotFound, Unsupported
from plexapi.library import LibrarySection
from plexapi.mixins import ArtMixin, PosterMixin
from plexapi.playqueue import PlayQueue
from plexapi.utils import cast, toDatetime


@utils.registerPlexObject
class Playlist(PlexPartialObject, Playable):
class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin):
""" Represents a single Playlist.

Attributes:
Expand Down Expand Up @@ -311,41 +312,3 @@ def sync(self, videoQuality=None, photoResolution=None, audioBitrate=None, clien
raise Unsupported('Unsupported playlist content')

return myplex.sync(sync_item, client=client, clientId=clientId)

def posters(self):
""" Returns list of available poster objects. :class:`~plexapi.media.Poster`. """

return self.fetchItems('/library/metadata/%s/posters' % self.ratingKey)

def uploadPoster(self, url=None, filepath=None):
""" Upload poster from url or filepath. :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video`. """
if url:
key = '/library/metadata/%s/posters?url=%s' % (self.ratingKey, quote_plus(url))
self._server.query(key, method=self._server._session.post)
elif filepath:
key = '/library/metadata/%s/posters?' % self.ratingKey
data = open(filepath, 'rb').read()
self._server.query(key, method=self._server._session.post, data=data)

def setPoster(self, poster):
""" Set . :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video` """
poster.select()

def arts(self):
""" Returns list of available art objects. :class:`~plexapi.media.Poster`. """

return self.fetchItems('/library/metadata/%s/arts' % self.ratingKey)

def uploadArt(self, url=None, filepath=None):
""" Upload art from url or filepath. :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video`. """
if url:
key = '/library/metadata/%s/arts?url=%s' % (self.ratingKey, quote_plus(url))
self._server.query(key, method=self._server._session.post)
elif filepath:
key = '/library/metadata/%s/arts?' % self.ratingKey
data = open(filepath, 'rb').read()
self._server.query(key, method=self._server._session.post, data=data)

def setArt(self, art):
""" Set :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video` """
art.select()
17 changes: 9 additions & 8 deletions plexapi/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from plexapi import library, media, settings, utils
from plexapi.base import Playable, PlexPartialObject
from plexapi.exceptions import BadRequest, NotFound
from plexapi.mixins import SplitMergeMixin, UnmatchMatchMixin
from plexapi.mixins import CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, EditWriter
from plexapi.mixins import ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin
from plexapi.mixins import CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, WriterMixin


class Video(PlexPartialObject):
Expand Down Expand Up @@ -261,8 +261,8 @@ def sync(self, videoQuality, client=None, clientId=None, limit=None, unwatched=F


@utils.registerPlexObject
class Movie(Playable, Video, SplitMergeMixin, UnmatchMatchMixin,
CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, EditWriter):
class Movie(Video, Playable, ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin,
CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, WriterMixin):
""" Represents a single Movie.

Attributes:
Expand Down Expand Up @@ -388,7 +388,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs):


@utils.registerPlexObject
class Show(Video, SplitMergeMixin, UnmatchMatchMixin,
class Show(Video, ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin,
CollectionMixin, GenreMixin, LabelMixin):
""" Represents a single Show (including all seasons and episodes).

Expand Down Expand Up @@ -587,7 +587,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs):


@utils.registerPlexObject
class Season(Video):
class Season(Video, ArtMixin, PosterMixin):
""" Represents a single Show Season (including all episodes).

Attributes:
Expand Down Expand Up @@ -713,7 +713,8 @@ def _defaultSyncTitle(self):


@utils.registerPlexObject
class Episode(Playable, Video, DirectorMixin, EditWriter):
class Episode(Video, Playable, ArtMixin, PosterMixin,
DirectorMixin, WriterMixin):
""" Represents a single Shows Episode.

Attributes:
Expand Down Expand Up @@ -850,7 +851,7 @@ def _defaultSyncTitle(self):


@utils.registerPlexObject
class Clip(Playable, Video):
class Clip(Video, Playable):
"""Represents a single Clip.

Attributes:
Expand Down
Loading