Basic support for photos complete

This commit is contained in:
Michael Shepanski 2016-04-09 23:59:47 -04:00
parent 81e22147c0
commit 09a7ae80db
6 changed files with 134 additions and 63 deletions

View file

@ -26,6 +26,7 @@ class Library(object):
MovieSection.TYPE: MovieSection,
ShowSection.TYPE: ShowSection,
MusicSection.TYPE: MusicSection,
PhotoSection.TYPE: PhotoSection,
}
path = '/library/sections'
for elem in self.server.query(path):
@ -98,6 +99,7 @@ class LibrarySection(object):
self.title = data.attrib.get('title')
self.scanner = data.attrib.get('scanner')
self.language = data.attrib.get('language')
# TODO: Add Location
def __repr__(self):
title = self.title.replace(' ','.')[0:20]
@ -242,16 +244,28 @@ class MusicSection(LibrarySection):
ALLOWED_SORT = ('addedAt', 'lastViewedAt', 'viewCount', 'titleSort')
TYPE = 'artist'
def searchShows(self, **kwargs):
def searchArtists(self, **kwargs):
return self.search(libtype='artist', **kwargs)
def searchEpisodes(self, **kwargs):
def searchAlbums(self, **kwargs):
return self.search(libtype='album', **kwargs)
def searchTracks(self, **kwargs):
return self.search(libtype='track', **kwargs)
class PhotoSection(LibrarySection):
ALLOWED_FILTERS = ()
ALLOWED_SORT = ()
TYPE = 'photo'
def searchAlbums(self, **kwargs):
return self.search(libtype='photo', **kwargs)
def searchPhotos(self, **kwargs):
return self.search(libtype='photo', **kwargs)
@utils.register_libtype
class FilterChoice(object):
TYPE = 'Directory'

View file

@ -12,20 +12,20 @@ class Media(object):
self.server = server
self.initpath = initpath
self.video = video
self.videoResolution = data.attrib.get('videoResolution')
self.id = cast(int, data.attrib.get('id'))
self.duration = cast(int, data.attrib.get('duration'))
self.bitrate = cast(int, data.attrib.get('bitrate'))
self.width = cast(int, data.attrib.get('width'))
self.height = cast(int, data.attrib.get('height'))
self.aspectRatio = cast(float, data.attrib.get('aspectRatio'))
self.audioChannels = cast(int, data.attrib.get('audioChannels'))
self.audioCodec = data.attrib.get('audioCodec')
self.videoCodec = data.attrib.get('videoCodec')
self.bitrate = cast(int, data.attrib.get('bitrate'))
self.container = data.attrib.get('container')
self.videoFrameRate = data.attrib.get('videoFrameRate')
self.optimizedForStreaming = cast(bool, data.attrib.get('optimizedForStreaming'))
self.duration = cast(int, data.attrib.get('duration'))
self.height = cast(int, data.attrib.get('height'))
self.id = cast(int, data.attrib.get('id'))
self.optimizedForStreaming = cast(bool, data.attrib.get('has64bitOffsets'))
self.optimizedForStreaming = cast(bool, data.attrib.get('optimizedForStreaming'))
self.videoCodec = data.attrib.get('videoCodec')
self.videoFrameRate = data.attrib.get('videoFrameRate')
self.videoResolution = data.attrib.get('videoResolution')
self.width = cast(int, data.attrib.get('width'))
self.parts = [MediaPart(server, e, initpath, self) for e in data]
def __repr__(self):
@ -40,12 +40,12 @@ class MediaPart(object):
self.server = server
self.initpath = initpath
self.media = media
self.id = cast(int, data.attrib.get('id'))
self.key = data.attrib.get('key')
self.container = data.attrib.get('container')
self.duration = cast(int, data.attrib.get('duration'))
self.file = data.attrib.get('file')
self.id = cast(int, data.attrib.get('id'))
self.key = data.attrib.get('key')
self.size = cast(int, data.attrib.get('size'))
self.container = data.attrib.get('container')
self.streams = [MediaPartStream.parse(self.server, e, self.initpath, self) for e in data if e.tag == 'Stream']
def __repr__(self):
@ -94,8 +94,8 @@ class VideoStream(MediaPartStream):
def __init__(self, server, data, initpath, part):
super(VideoStream, self).__init__(server, data, initpath, part)
self.bitrate = cast(int, data.attrib.get('bitrate'))
self.bitDepth = cast(int, data.attrib.get('bitDepth'))
self.bitrate = cast(int, data.attrib.get('bitrate'))
self.cabac = cast(int, data.attrib.get('cabac'))
self.chromaSubsampling = data.attrib.get('chromaSubsampling')
self.colorSpace = data.attrib.get('colorSpace')
@ -119,10 +119,10 @@ class AudioStream(MediaPartStream):
def __init__(self, server, data, initpath, part):
super(AudioStream, self).__init__(server, data, initpath, part)
self.audioChannelLayout = data.attrib.get('audioChannelLayout')
self.channels = cast(int, data.attrib.get('channels'))
self.bitrate = cast(int, data.attrib.get('bitrate'))
self.bitDepth = cast(int, data.attrib.get('bitDepth'))
self.bitrate = cast(int, data.attrib.get('bitrate'))
self.bitrateMode = data.attrib.get('bitrateMode')
self.channels = cast(int, data.attrib.get('channels'))
self.dialogNorm = cast(int, data.attrib.get('dialogNorm'))
self.duration = cast(int, data.attrib.get('duration'))
self.samplingRate = cast(int, data.attrib.get('samplingRate'))
@ -135,8 +135,8 @@ class SubtitleStream(MediaPartStream):
def __init__(self, server, data, initpath, part):
super(SubtitleStream, self).__init__(server, data, initpath, part)
self.key = data.attrib.get('key')
self.format = data.attrib.get('format')
self.key = data.attrib.get('key')
self.title = data.attrib.get('title')
@ -145,22 +145,22 @@ class TranscodeSession(object):
def __init__(self, server, data):
self.server = server
self.key = data.attrib.get('key')
self.throttled = cast(int, data.attrib.get('throttled'))
self.progress = cast(float, data.attrib.get('progress'))
self.speed = cast(int, data.attrib.get('speed'))
self.duration = cast(int, data.attrib.get('duration'))
self.remaining = cast(int, data.attrib.get('remaining'))
self.context = data.attrib.get('context')
self.videoDecision = data.attrib.get('videoDecision')
self.audioDecision = data.attrib.get('audioDecision')
self.protocol = data.attrib.get('protocol')
self.container = data.attrib.get('container')
self.videoCodec = data.attrib.get('videoCodec')
self.audioCodec = data.attrib.get('audioCodec')
self.audioChannels = cast(int, data.attrib.get('audioChannels'))
self.width = cast(int, data.attrib.get('width'))
self.audioCodec = data.attrib.get('audioCodec')
self.audioDecision = data.attrib.get('audioDecision')
self.container = data.attrib.get('container')
self.context = data.attrib.get('context')
self.duration = cast(int, data.attrib.get('duration'))
self.height = cast(int, data.attrib.get('height'))
self.key = data.attrib.get('key')
self.progress = cast(float, data.attrib.get('progress'))
self.protocol = data.attrib.get('protocol')
self.remaining = cast(int, data.attrib.get('remaining'))
self.speed = cast(int, data.attrib.get('speed'))
self.throttled = cast(int, data.attrib.get('throttled'))
self.videoCodec = data.attrib.get('videoCodec')
self.videoDecision = data.attrib.get('videoDecision')
self.width = cast(int, data.attrib.get('width'))
class MediaTag(object):
@ -169,8 +169,8 @@ class MediaTag(object):
def __init__(self, server, data):
self.server = server
self.id = cast(int, data.attrib.get('id'))
self.tag = data.attrib.get('tag')
self.role = data.attrib.get('role')
self.tag = data.attrib.get('tag')
def __repr__(self):
tag = self.tag.replace(' ','.')[0:20]

66
plexapi/photo.py Normal file
View file

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
"""
PlexPhoto
"""
from plexapi import media, utils
from plexapi.utils import PlexPartialObject
NA = utils.NA
@utils.register_libtype
class Photoalbum(PlexPartialObject):
TYPE = 'photoalbum'
def __init__(self, server, data, initpath):
super(Photoalbum, self).__init__(data, initpath, server)
def _loadData(self, data):
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.art = data.attrib.get('art', NA)
self.composite = data.attrib.get('composite', NA)
self.guid = data.attrib.get('guid', NA)
self.index = utils.cast(int, data.attrib.get('index', NA))
self.key = data.attrib.get('key', NA)
self.librarySectionID = data.attrib.get('librarySectionID', NA)
self.ratingKey = data.attrib.get('ratingKey', NA)
self.summary = data.attrib.get('summary', NA)
self.thumb = data.attrib.get('thumb', NA)
self.title = data.attrib.get('title', NA)
self.type = data.attrib.get('type', NA)
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA))
def photos(self):
path = '/library/metadata/%s/children' % self.ratingKey
return utils.listItems(self.server, path, Photo.TYPE)
def photo(self, title):
path = '/library/metadata/%s/children' % self.ratingKey
return utils.findItem(self.server, path, title)
@utils.register_libtype
class Photo(PlexPartialObject):
TYPE = 'photo'
def __init__(self, server, data, initpath):
super(Photo, self).__init__(data, initpath, server)
def _loadData(self, data):
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.index = utils.cast(int, data.attrib.get('index', NA))
self.key = data.attrib.get('key', NA)
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.parentKey = data.attrib.get('parentKey', NA)
self.parentRatingKey = data.attrib.get('parentRatingKey', NA)
self.ratingKey = data.attrib.get('ratingKey', NA)
self.summary = data.attrib.get('summary', NA)
self.thumb = data.attrib.get('thumb', NA)
self.title = data.attrib.get('title', NA)
self.type = data.attrib.get('type', NA)
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA))
self.year = utils.cast(int, data.attrib.get('year', NA))
if self.isFullObject():
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE]
def photoalbum(self):
return utils.listItems(self.server, self.parentKey)[0]

View file

@ -6,7 +6,7 @@ import requests
from requests.status_codes import _codes as codes
from plexapi import BASE_HEADERS, TIMEOUT
from plexapi import log, utils
from plexapi import audio, video, playlist # noqa; required
from plexapi import audio, video, photo, playlist # noqa; required
from plexapi.compat import quote
from plexapi.client import PlexClient
from plexapi.exceptions import BadRequest, NotFound

View file

@ -123,6 +123,8 @@ class Playable(object):
def buildItem(server, elem, initpath, bytag=False):
libtype = elem.tag if bytag else elem.attrib.get('type')
if libtype == 'photo' and elem.tag == 'Directory':
libtype = 'photoalbum'
if libtype in LIBRARY_TYPES:
cls = LIBRARY_TYPES[libtype]
return cls(server, elem, initpath)

View file

@ -8,10 +8,9 @@ run this test suite with the following command:
>> python tests.py -u <USERNAME> -p <PASSWORD> -s <SERVERNAME>
"""
import argparse, sys, time
from os.path import dirname, abspath
from os.path import basename, dirname, abspath
sys.path.append(dirname(dirname(abspath(__file__))))
from utils import log, register, safe_client, run_tests
from plexapi.myplex import MyPlexAccount
from plexapi.utils import NA
SHOW_SECTION = 'TV Shows'
@ -24,6 +23,8 @@ AUDIO_SECTION = 'Music'
AUDIO_ARTIST = 'Beastie Boys'
AUDIO_ALBUM = 'Licensed To Ill'
AUDIO_TRACK = 'Brass Monkey'
PHOTO_SECTION = 'Photos'
PHOTO_ALBUM = '2015-12-12 - Family Photo for Christmas card'
CLIENT = 'pkkid-home'
CLIENT_BASEURL = 'http://192.168.1.131:3005'
@ -79,33 +80,6 @@ def test_sessions(plex, account=None):
movie.markWatched()
# try:
# mtype = 'video'
# movie = plex.library.section(MOVIE_SECTION).get(MOVIE_TITLE)
# subs = [s for s in movie.subtitleStreams if s.language == 'English']
# log(2, 'Client: %s (%s)' % (client.title, client.product))
# log(2, 'Capabilities: %s' % client.protocolCapabilities)
# log(2, 'Playing to %s..' % movie.title)
# client.playMedia(movie); time.sleep(5)
# log(2, 'Pause..')
# client.pause(mtype); time.sleep(2)
# log(2, 'Step Forward..')
# client.stepForward(mtype); time.sleep(5)
# log(2, 'Play..')
# client.play(mtype); time.sleep(3)
# log(2, 'Seek to 10m..')
# client.seekTo(10*60*1000); time.sleep(5)
# log(2, 'Disable Subtitles..')
# client.setSubtitleStream(0, mtype); time.sleep(10)
# log(2, 'Load English Subtitles %s..' % subs[0].id)
# client.setSubtitleStream(subs[0].id, mtype); time.sleep(10)
# log(2, 'Stop..')
#
# finally:
# log(2, 'Cleanup: Marking %s watched.' % movie.title)
# movie.markWatched()
#-----------------------
# Search
#-----------------------
@ -417,6 +391,21 @@ def test_stream_url(plex, account=None):
log(2, 'Track: cvlc "%s"' % track.getStreamURL())
@register('photo')
def test_list_photoalbums(plex, account=None):
photosection = plex.library.section(PHOTO_SECTION)
photoalbums = photosection.all()
log(2, 'Listing albums..')
for album in photoalbums[:10]:
log(4, '%s' % album.title)
assert len(photoalbums), 'No photoalbums found.'
album = photosection.get(PHOTO_ALBUM)
photos = album.photos()
for photo in photos[:10]:
log(4, '%s (%sx%s)' % (basename(photo.media[0].parts[0].file), photo.media[0].width, photo.media[0].height))
assert len(photoalbums), 'No photos found.'
#-----------------------
# Play Queue
#-----------------------