mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 06:04:15 +00:00
Basic support for photos complete
This commit is contained in:
parent
81e22147c0
commit
09a7ae80db
6 changed files with 134 additions and 63 deletions
|
@ -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'
|
||||
|
|
|
@ -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
66
plexapi/photo.py
Normal 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]
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
#-----------------------
|
||||
|
|
Loading…
Reference in a new issue