mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-22 19:53:17 +00:00
commit
6e761f0fc5
26 changed files with 2354 additions and 62 deletions
9
.coveragerc
Normal file
9
.coveragerc
Normal file
|
@ -0,0 +1,9 @@
|
|||
[report]
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
raise NotImplementedError
|
||||
raise Unsupported
|
||||
except ImportError
|
||||
def __repr__
|
||||
def __bool__
|
||||
if __name__ == .__main__.:
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -17,3 +17,7 @@ include/
|
|||
lib/
|
||||
pip-selfcheck.json
|
||||
pyvenv.cfg
|
||||
|
||||
htmlcov
|
||||
.coverage
|
||||
*.orig
|
14
.travis.yml
14
.travis.yml
|
@ -2,10 +2,18 @@ language: python
|
|||
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
|
||||
before_install:
|
||||
- pip install -U pytest pytest-cov coveralls
|
||||
|
||||
install:
|
||||
- pip install .
|
||||
- pip install -r requirements.pip
|
||||
- pip install -r requirements_dev.txt
|
||||
|
||||
script: python tests/runtests.py --query=test_core/test_server
|
||||
script: py.test tests/tests_pytest --cov-config .coveragerc --cov=plexapi --cov-report=html
|
||||
|
||||
after_success:
|
||||
- coveralls
|
|
@ -131,6 +131,16 @@ class Artist(Audio):
|
|||
""" Alias of :func:`~plexapi.audio.Artist.track`. """
|
||||
return self.track(title)
|
||||
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
downloaded = []
|
||||
for album in self.albums():
|
||||
for track in album.tracks():
|
||||
dl = track.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
|
||||
if dl:
|
||||
downloaded.extend(dl)
|
||||
|
||||
return downloaded
|
||||
|
||||
|
||||
@utils.register_libtype
|
||||
class Album(Audio):
|
||||
|
@ -192,6 +202,15 @@ class Album(Audio):
|
|||
""" Return :func:`~plexapi.audio.Artist` of this album. """
|
||||
return utils.listItems(self.server, self.parentKey)[0]
|
||||
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
downloaded = []
|
||||
for ep in self.tracks():
|
||||
dl = ep.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
|
||||
if dl:
|
||||
downloaded.extend(dl)
|
||||
|
||||
return downloaded
|
||||
|
||||
|
||||
@utils.register_libtype
|
||||
class Track(Audio, Playable):
|
||||
|
@ -255,9 +274,13 @@ class Track(Audio, Playable):
|
|||
self.ratingCount = utils.cast(int, data.attrib.get('ratingCount', NA))
|
||||
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
|
||||
self.year = utils.cast(int, data.attrib.get('year', NA))
|
||||
# media is included in /children
|
||||
self.media = [media.Media(self.server, e, self.initpath, self)
|
||||
for e in data if e.tag == media.Media.TYPE]
|
||||
if self.isFullObject(): # check me
|
||||
self.moods = [media.Mood(self.server, e) for e in data if e.tag == media.Mood.TYPE]
|
||||
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE]
|
||||
#self.media = [media.Media(self.server, e, self.initpath, self)
|
||||
# for e in data if e.tag == media.Media.TYPE]
|
||||
# data for active sessions and history
|
||||
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey', NA))
|
||||
self.username = utils.findUsername(data)
|
||||
|
@ -277,3 +300,39 @@ class Track(Audio, Playable):
|
|||
def artist(self):
|
||||
""" Return this track's :class:`~plexapi.audio.Artist`. """
|
||||
return utils.listItems(self.server, self.grandparentKey)[0]
|
||||
|
||||
def _prettyfilename(self):
|
||||
return '%s - %s %s' % (self.grandparentTitle, self.parentTitle, self.title)
|
||||
|
||||
'''
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
"""Download a episode. If kwargs are passed your can download a trancoded file.
|
||||
|
||||
Args:
|
||||
savepath (str): Abs path to savefolder
|
||||
keep_orginal_name (bool): Use the mediafiles orginal name
|
||||
|
||||
kwargs:
|
||||
See getStreamURL docs.
|
||||
|
||||
"""
|
||||
downloaded = []
|
||||
locs = [i for i in self.iterParts() if i]
|
||||
for loc in locs:
|
||||
if keep_orginal_name is False:
|
||||
name = '%s.%s' % (self._prettyfilename(), loc.container)
|
||||
else:
|
||||
name = loc.file
|
||||
|
||||
# So this seems to be a alot slower but allows transcode.
|
||||
if kwargs:
|
||||
download_url = self.getStreamURL(**kwargs)
|
||||
else:
|
||||
download_url = self.server.url('%s?download=1' % loc.key)
|
||||
|
||||
dl = utils.download(download_url, filename=name, savepath=savepath, session=self.server.session)
|
||||
if dl:
|
||||
downloaded.append(dl)
|
||||
|
||||
return downloaded
|
||||
'''
|
||||
|
|
|
@ -45,8 +45,12 @@ class PlexClient(object):
|
|||
def __init__(self, baseurl, token=None, session=None, server=None, data=None):
|
||||
self.baseurl = baseurl.strip('/')
|
||||
self.token = token
|
||||
self.session = session or requests.Session()
|
||||
self.server = server
|
||||
# session > server.session > requests.Session
|
||||
if server:
|
||||
self.session = session or server.session
|
||||
else:
|
||||
self.session = session or requests.Session()
|
||||
self._loadData(data) if data is not None else self.connect()
|
||||
self._proxyThroughServer = False
|
||||
self._commandId = 0
|
||||
|
|
|
@ -23,3 +23,6 @@ class Unsupported(PlexApiException):
|
|||
class Unauthorized(PlexApiException):
|
||||
""" Invalid username or password. """
|
||||
pass
|
||||
|
||||
class NotImplementedError(PlexApiException):
|
||||
pass
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from plexapi import log, utils
|
||||
from plexapi import X_PLEX_CONTAINER_SIZE
|
||||
from plexapi import X_PLEX_CONTAINER_SIZE, log, utils
|
||||
from plexapi.compat import unquote
|
||||
from plexapi.media import MediaTag
|
||||
from plexapi.exceptions import BadRequest, NotFound
|
||||
from plexapi.media import MediaTag
|
||||
|
||||
|
||||
class Library(object):
|
||||
|
@ -67,11 +66,11 @@ class Library(object):
|
|||
|
||||
def sectionByID(self, sectionID):
|
||||
""" Returns the :class:`~plexapi.library.LibrarySection` that matches the specified sectionID.
|
||||
|
||||
|
||||
Parameters:
|
||||
sectionID (int): ID of the section to return.
|
||||
"""
|
||||
if not self._sectionsByID:
|
||||
if not self._sectionsByID or sectionID not in self._sectionsByID:
|
||||
self.sections()
|
||||
return self._sectionsByID[sectionID]
|
||||
|
||||
|
@ -89,16 +88,18 @@ class Library(object):
|
|||
""" Returns a list of all media items recently added. """
|
||||
return utils.listItems(self.server, '/library/recentlyAdded')
|
||||
|
||||
def get(self, title):
|
||||
""" Return the first item from all items with the specified title.
|
||||
def get(self, title): # this should use hub search when its merged
|
||||
""" Return the first item from all items with the specified title.
|
||||
|
||||
Parameters:
|
||||
title (str): Title of the item to return.
|
||||
"""
|
||||
return utils.findItem(self.server, '/library/all', title)
|
||||
for i in self.all():
|
||||
if i.title.lower() == title.lower():
|
||||
return i
|
||||
|
||||
def getByKey(self, key):
|
||||
""" Return the first item from all items with the specified key.
|
||||
""" Return the first item from all items with the specified key.
|
||||
|
||||
Parameters:
|
||||
key (str): Key of the item to return.
|
||||
|
@ -131,6 +132,8 @@ class Library(object):
|
|||
server will automatically clean up old bundles once a week as part of Scheduled Tasks.
|
||||
"""
|
||||
self.server.query('/library/clean/bundles')
|
||||
# Should this return true or false?
|
||||
# check element if if has the correct mediaprefix?
|
||||
|
||||
def emptyTrash(self):
|
||||
""" If a library has items in the Library Trash, use this option to empty the Trash. """
|
||||
|
@ -226,7 +229,7 @@ class LibrarySection(object):
|
|||
|
||||
def recentlyAdded(self, maxresults=50):
|
||||
""" Returns a list of media items recently added from this library section.
|
||||
|
||||
|
||||
Parameters:
|
||||
maxresults (int): Max number of items to return (default 50).
|
||||
"""
|
||||
|
@ -234,7 +237,7 @@ class LibrarySection(object):
|
|||
|
||||
def analyze(self):
|
||||
""" Run an analysis on all of the items in this library section. """
|
||||
self.server.query('/library/sections/%s/analyze' % self.key)
|
||||
self.server.query('/library/sections/%s/analyze' % self.key, method=self.server.session.put)
|
||||
|
||||
def emptyTrash(self):
|
||||
""" If a section has items in the Trash, use this option to empty the Trash. """
|
||||
|
@ -366,7 +369,7 @@ class LibrarySection(object):
|
|||
|
||||
class MovieSection(LibrarySection):
|
||||
""" Represents a :class:`~plexapi.library.LibrarySection` section containing movies.
|
||||
|
||||
|
||||
Attributes:
|
||||
ALLOWED_FILTERS (list<str>): List of allowed search filters. ('unwatched',
|
||||
'duplicate', 'year', 'decade', 'genre', 'contentRating', 'collection',
|
||||
|
@ -377,15 +380,15 @@ class MovieSection(LibrarySection):
|
|||
TYPE (str): 'movie'
|
||||
"""
|
||||
ALLOWED_FILTERS = ('unwatched', 'duplicate', 'year', 'decade', 'genre', 'contentRating',
|
||||
'collection', 'director', 'actor', 'country', 'studio', 'resolution')
|
||||
'collection', 'director', 'actor', 'country', 'studio', 'resolution')
|
||||
ALLOWED_SORT = ('addedAt', 'originallyAvailableAt', 'lastViewedAt', 'titleSort', 'rating',
|
||||
'mediaHeight', 'duration')
|
||||
'mediaHeight', 'duration')
|
||||
TYPE = 'movie'
|
||||
|
||||
|
||||
class ShowSection(LibrarySection):
|
||||
""" Represents a :class:`~plexapi.library.LibrarySection` section containing tv shows.
|
||||
|
||||
|
||||
Attributes:
|
||||
ALLOWED_FILTERS (list<str>): List of allowed search filters. ('unwatched',
|
||||
'year', 'genre', 'contentRating', 'network', 'collection')
|
||||
|
@ -395,7 +398,7 @@ class ShowSection(LibrarySection):
|
|||
"""
|
||||
ALLOWED_FILTERS = ('unwatched', 'year', 'genre', 'contentRating', 'network', 'collection')
|
||||
ALLOWED_SORT = ('addedAt', 'lastViewedAt', 'originallyAvailableAt', 'titleSort',
|
||||
'rating', 'unwatched')
|
||||
'rating', 'unwatched')
|
||||
TYPE = 'show'
|
||||
|
||||
def searchShows(self, **kwargs):
|
||||
|
@ -408,7 +411,7 @@ class ShowSection(LibrarySection):
|
|||
|
||||
def recentlyAdded(self, libtype='episode', maxresults=50):
|
||||
""" Returns a list of recently added episodes from this library section.
|
||||
|
||||
|
||||
Parameters:
|
||||
maxresults (int): Max number of items to return (default 50).
|
||||
"""
|
||||
|
@ -417,7 +420,7 @@ class ShowSection(LibrarySection):
|
|||
|
||||
class MusicSection(LibrarySection):
|
||||
""" Represents a :class:`~plexapi.library.LibrarySection` section containing music artists.
|
||||
|
||||
|
||||
Attributes:
|
||||
ALLOWED_FILTERS (list<str>): List of allowed search filters. ('genre',
|
||||
'country', 'collection')
|
||||
|
@ -448,23 +451,25 @@ class MusicSection(LibrarySection):
|
|||
|
||||
class PhotoSection(LibrarySection):
|
||||
""" Represents a :class:`~plexapi.library.LibrarySection` section containing photos.
|
||||
|
||||
|
||||
Attributes:
|
||||
ALLOWED_FILTERS (list<str>): List of allowed search filters. <NONE>
|
||||
ALLOWED_SORT (list<str>): List of allowed sorting keys. <NONE>
|
||||
TYPE (str): 'photo'
|
||||
"""
|
||||
ALLOWED_FILTERS = ()
|
||||
ALLOWED_FILTERS = ('all', 'iso', 'make', 'lens', 'aperture', 'exposure')
|
||||
ALLOWED_SORT = ()
|
||||
TYPE = 'photo'
|
||||
|
||||
def searchAlbums(self, **kwargs):
|
||||
def searchAlbums(self, title, **kwargs): # lets use this for now.
|
||||
""" Search for an album. See :func:`~plexapi.library.LibrarySection.search()` for usage. """
|
||||
return self.search(libtype='photo', **kwargs)
|
||||
albums = utils.listItems(self.server, '/library/sections/%s/all?type=14' % self.key)
|
||||
return [i for i in albums if i.title.lower() == title.lower()]
|
||||
|
||||
def searchPhotos(self, **kwargs):
|
||||
def searchPhotos(self, title, **kwargs):
|
||||
""" Search for a photo. See :func:`~plexapi.library.LibrarySection.search()` for usage. """
|
||||
return self.search(libtype='photo', **kwargs)
|
||||
photos = utils.listItems(self.server, '/library/sections/%s/all?type=13' % self.key)
|
||||
return [i for i in photos if i.title.lower() == title.lower()]
|
||||
|
||||
|
||||
@utils.register_libtype
|
||||
|
@ -479,7 +484,7 @@ class FilterChoice(object):
|
|||
fastKey (str): API path to quickly list all items in this filter
|
||||
(/library/sections/<section>/all?genre=<key>)
|
||||
key (str): Short key (id) of this filter option (used ad <key> in fastKey above).
|
||||
thumb (str): Thumbnail used to represent this filter option.
|
||||
thumb (str): Thumbnail used to represent this filter option.
|
||||
title (str): Human readable name for this filter option.
|
||||
type (str): Filter type (genre, contentRating, etc).
|
||||
"""
|
||||
|
|
|
@ -45,7 +45,11 @@ class MyPlexAccount(object):
|
|||
BASEURL = 'https://plex.tv/users/account'
|
||||
SIGNIN = 'https://my.plexapp.com/users/sign_in.xml'
|
||||
|
||||
def __init__(self, data, initpath=None):
|
||||
def __init__(self, data=None, initpath=None, username=None, password=None, session=None):
|
||||
if data is None and username and password:
|
||||
self.Signin(username, password)#
|
||||
|
||||
self._session = session or requests.Session()
|
||||
self.authenticationToken = data.attrib.get('authenticationToken')
|
||||
if self.authenticationToken:
|
||||
logfilter.add_secret(self.authenticationToken)
|
||||
|
@ -116,7 +120,7 @@ class MyPlexAccount(object):
|
|||
return _findItem(self.users(), email, ['username', 'email'])
|
||||
|
||||
@classmethod
|
||||
def signin(cls, username, password):
|
||||
def signin(cls, username, password, session=None):
|
||||
""" Returns a new :class:`~myplex.MyPlexAccount` object by connecting to MyPlex with the
|
||||
specified username and password. This is essentially logging into MyPlex and often
|
||||
the very first entry point to using this API.
|
||||
|
@ -133,7 +137,8 @@ class MyPlexAccount(object):
|
|||
del plexapi.BASE_HEADERS['X-Plex-Token']
|
||||
auth = (username, password)
|
||||
log.info('POST %s', cls.SIGNIN)
|
||||
response = requests.post(
|
||||
sess = session or requests.Session()
|
||||
response = sess.post(
|
||||
cls.SIGNIN, headers=plexapi.BASE_HEADERS, auth=auth, timeout=TIMEOUT)
|
||||
if response.status_code != requests.codes.created:
|
||||
codename = codes.get(response.status_code)[0]
|
||||
|
@ -142,7 +147,7 @@ class MyPlexAccount(object):
|
|||
(response.status_code, codename))
|
||||
raise BadRequest('(%s) %s' % (response.status_code, codename))
|
||||
data = ElementTree.fromstring(response.text.encode('utf8'))
|
||||
return cls(data, cls.SIGNIN)
|
||||
return MyPlexAccount(data, cls.SIGNIN, session=sess)
|
||||
|
||||
|
||||
class MyPlexUser(object):
|
||||
|
|
|
@ -57,7 +57,7 @@ class Playlist(PlexPartialObject, Playable):
|
|||
for item in items:
|
||||
if item.listType != self.playlistType:
|
||||
raise BadRequest('Can not mix media types when building a playlist: %s and %s' % (self.playlistType, item.listType))
|
||||
ratingKeys.append(item.ratingKey)
|
||||
ratingKeys.append(str(item.ratingKey))
|
||||
uuid = items[0].section().uuid
|
||||
ratingKeys = ','.join(ratingKeys)
|
||||
path = '%s/items%s' % (self.key, utils.joinArgs({
|
||||
|
|
|
@ -206,9 +206,10 @@ class PlexServer(object):
|
|||
if headers:
|
||||
h.update(headers)
|
||||
response = method(url, headers=h, timeout=TIMEOUT, **kwargs)
|
||||
if response.status_code not in [200, 201]:
|
||||
#print(response.url)
|
||||
if response.status_code not in [200, 201]: # pragma: no cover
|
||||
codename = codes.get(response.status_code)[0]
|
||||
raise BadRequest('(%s) %s' % (response.status_code, codename))
|
||||
raise BadRequest('(%s) %s %s' % (response.status_code, codename, response.url))
|
||||
data = response.text.encode('utf8')
|
||||
return ElementTree.fromstring(data) if data else None
|
||||
|
||||
|
|
148
plexapi/utils.py
148
plexapi/utils.py
|
@ -1,13 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging, re
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from plexapi.compat import quote, urlencode, string_type
|
||||
from plexapi.exceptions import NotFound, UnknownType, Unsupported
|
||||
from threading import Thread
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
from plexapi.compat import quote, string_type, urlencode
|
||||
from plexapi.exceptions import NotFound, NotImplementedError, UnknownType, Unsupported
|
||||
#from plexapi import log
|
||||
|
||||
|
||||
# Search Types - Plex uses these to filter specific media types when searching.
|
||||
SEARCHTYPES = {'movie': 1, 'show': 2, 'season': 3, 'episode': 4,
|
||||
'artist': 8, 'album': 9, 'track': 10}
|
||||
SEARCHTYPES = {'movie': 1, 'show': 2, 'season': 3,
|
||||
'episode': 4, 'artist': 8, 'album': 9, 'track': 10,
|
||||
'photo': 14}
|
||||
|
||||
|
||||
LIBRARY_TYPES = {}
|
||||
|
||||
|
||||
|
@ -20,7 +31,7 @@ def register_libtype(cls):
|
|||
return cls
|
||||
|
||||
|
||||
class NA(object):
|
||||
class _NA(object):
|
||||
""" This used to be a simple variable equal to '__NA__'. There has been need to
|
||||
compare NA against None in some use cases. This object allows the internals
|
||||
of PlexAPI to distinguish between unfetched values and fetched, but non-existent
|
||||
|
@ -31,7 +42,7 @@ class NA(object):
|
|||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, NA) or other in [None, '__NA__']
|
||||
return isinstance(other, _NA) or other in [None, '__NA__']
|
||||
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
|
@ -40,6 +51,10 @@ class NA(object):
|
|||
return '__NA__'
|
||||
|
||||
|
||||
# Lets do this for now.
|
||||
NA = _NA()
|
||||
|
||||
|
||||
class SecretsFilter(logging.Filter):
|
||||
""" Logging filter to hide secrets. """
|
||||
def __init__(self, secrets=None):
|
||||
|
@ -73,6 +88,7 @@ class PlexPartialObject(object):
|
|||
self.server = server
|
||||
self.initpath = initpath
|
||||
self._loadData(data)
|
||||
self._reloaded = False
|
||||
|
||||
def __eq__(self, other):
|
||||
return other is not None and self.key == other.key
|
||||
|
@ -87,6 +103,7 @@ class PlexPartialObject(object):
|
|||
# Auto reload self, from the full key (path) when needed.
|
||||
if attr == 'key' or self.__dict__.get(attr) or self.isFullObject():
|
||||
return self.__dict__.get(attr, NA)
|
||||
print('reload because of %s' % attr)
|
||||
self.reload()
|
||||
return self.__dict__.get(attr, NA)
|
||||
|
||||
|
@ -95,14 +112,14 @@ class PlexPartialObject(object):
|
|||
self.__dict__[attr] = value
|
||||
|
||||
def _loadData(self, data):
|
||||
raise Exception('Abstract method not implemented.')
|
||||
raise NotImplementedError('Abstract method not implemented.')
|
||||
|
||||
def isFullObject(self):
|
||||
""" Retruns True if this is already a full object. A full object means all attributes
|
||||
were populated from the api path representing only this item. For example, the
|
||||
search result for a movie often only contain a portion of the attributes a full
|
||||
object (main url) for that movie contain.
|
||||
"""
|
||||
"""
|
||||
return not self.key or self.key == self.initpath
|
||||
|
||||
def isPartialObject(self):
|
||||
|
@ -114,6 +131,8 @@ class PlexPartialObject(object):
|
|||
data = self.server.query(self.key)
|
||||
self.initpath = self.key
|
||||
self._loadData(data[0])
|
||||
self._reloaded = True
|
||||
return self
|
||||
|
||||
|
||||
class Playable(object):
|
||||
|
@ -123,7 +142,7 @@ class Playable(object):
|
|||
|
||||
Attributes:
|
||||
player (:class:`~plexapi.client.PlexClient`): Client object playing this item (for active sessions).
|
||||
playlistItemID (int): Playlist item ID (only populated for :class:`~plexapi.playlist.Playlist` items).
|
||||
playlistItemID (int): Playlist item ID (only populated for :class:`~plexapi.playlist.Playlist` items).
|
||||
sessionKey (int): Active session key.
|
||||
transcodeSession (:class:`~plexapi.media.TranscodeSession`): Transcode Session object
|
||||
if item is being transcoded (None otherwise).
|
||||
|
@ -170,7 +189,9 @@ class Playable(object):
|
|||
# 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'
|
||||
return self.server.url('/%s/:/transcode/universal/start.m3u8?%s' % (streamtype, urlencode(params)))
|
||||
# sort the keys since the randomness fucks with my tests..
|
||||
sorted_params = sorted(params.items(), key=lambda val: val[0])
|
||||
return self.server.url('/%s/:/transcode/universal/start.m3u8?%s' % (streamtype, urlencode(sorted_params)))
|
||||
|
||||
def iterParts(self):
|
||||
""" Iterates over the parts of this media item. """
|
||||
|
@ -186,6 +207,37 @@ class Playable(object):
|
|||
"""
|
||||
client.playMedia(self)
|
||||
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
"""Download a episode. If kwargs are passed your can download a trancoded file.
|
||||
|
||||
Args:
|
||||
savepath (str): Abs path to savefolder
|
||||
keep_orginal_name (bool): Use the mediafiles orginal name
|
||||
|
||||
kwargs:
|
||||
See getStreamURL docs.
|
||||
|
||||
"""
|
||||
downloaded = []
|
||||
locs = [i for i in self.iterParts() if i]
|
||||
for loc in locs:
|
||||
if keep_orginal_name is False:
|
||||
name = '%s.%s' % (self._prettyfilename(), loc.container)
|
||||
else:
|
||||
name = loc.file
|
||||
|
||||
# So this seems to be a alot slower but allows transcode.
|
||||
if kwargs:
|
||||
download_url = self.getStreamURL(**kwargs)
|
||||
else:
|
||||
download_url = self.server.url('%s?download=1' % loc.key)
|
||||
|
||||
dl = download(download_url, filename=name, savepath=savepath, session=self.server.session)
|
||||
if dl:
|
||||
downloaded.append(dl)
|
||||
|
||||
return downloaded
|
||||
|
||||
|
||||
def buildItem(server, elem, initpath, bytag=False):
|
||||
""" Factory function to build the objects used within the PlexAPI.
|
||||
|
@ -377,7 +429,7 @@ def listChoices(server, path):
|
|||
|
||||
def listItems(server, path, libtype=None, watched=None, bytag=False):
|
||||
""" Returns a list of object built from :func:`~plexapi.utils.buildItem()` found
|
||||
within the specified path.
|
||||
within the specified path.
|
||||
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
|
||||
|
@ -401,7 +453,7 @@ def listItems(server, path, libtype=None, watched=None, bytag=False):
|
|||
return items
|
||||
|
||||
|
||||
def rget(obj, attrstr, default=None, delim='.'):
|
||||
def rget(obj, attrstr, default=None, delim='.'): # pragma: no cover
|
||||
""" Returns the value at the specified attrstr location within a nexted tree of
|
||||
dicts, lists, tuples, functions, classes, etc. The lookup is done recursivley
|
||||
for each key in attrstr (split by by the delimiter) This function is heavily
|
||||
|
@ -466,6 +518,7 @@ def threaded(callback, listargs):
|
|||
threads[-1].start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
@ -482,3 +535,72 @@ def toDatetime(value, format=None):
|
|||
else:
|
||||
value = datetime.fromtimestamp(int(value))
|
||||
return value
|
||||
|
||||
|
||||
def download(url, filename=None, savepath=None, session=None, chunksize=4024, mocked=False):
|
||||
"""Helper to download a thumb, videofile or something.
|
||||
|
||||
Args:
|
||||
url (str): url where the content be reached
|
||||
filename (str): Filename of the downloaded file, default None
|
||||
savepath (str): Defaults to current working dir
|
||||
chunksize (int): What chunksize read/write at the time
|
||||
mocked (bool): Helper to do evertything except write the file.
|
||||
|
||||
Example:
|
||||
|
||||
>>> download(a_episode.getStreamURL(), a_episode.location)
|
||||
/path/to/file
|
||||
|
||||
Returns:
|
||||
/path/to/file or None
|
||||
|
||||
"""
|
||||
session = session or requests.Session()
|
||||
print('Mocked download %s' % mocked)
|
||||
|
||||
if savepath is None:
|
||||
savepath = os.getcwd()
|
||||
else:
|
||||
# Make sure the user supplied path exists
|
||||
try:
|
||||
os.makedirs(savepath)
|
||||
except OSError:
|
||||
if not os.path.isdir(savepath): # pragma: no cover
|
||||
raise
|
||||
|
||||
filename = os.path.basename(filename)
|
||||
fullpath = os.path.join(savepath, filename)
|
||||
|
||||
try:
|
||||
response = session.get(url, stream=True)
|
||||
|
||||
# images dont have a extention so we try
|
||||
# to guess it from content-type
|
||||
ext = os.path.splitext(fullpath)[-1]
|
||||
if ext:
|
||||
ext = ''
|
||||
else:
|
||||
cp = response.headers.get('content-type')
|
||||
|
||||
if cp:
|
||||
if 'image' in cp:
|
||||
ext = '.%s' % cp.split('/')[1]
|
||||
|
||||
fullpath = '%s%s' % (fullpath, ext)
|
||||
|
||||
if mocked:
|
||||
return fullpath
|
||||
|
||||
with open(fullpath, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=chunksize):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
#log.debug('Downloaded %s to %s from %s' % (filename, fullpath, url))
|
||||
|
||||
return fullpath
|
||||
|
||||
except Exception as e: # pragma: no cover
|
||||
print('e %s' % e)
|
||||
#log.exception('Failed to download %s to %s %s' % (url, fullpath, e))
|
||||
|
|
|
@ -53,7 +53,7 @@ class Video(PlexPartialObject):
|
|||
that are useful to know–whether it's a video file,
|
||||
a music track, or one of your photos.
|
||||
"""
|
||||
self.server.query('/%s/analyze' % self.key)
|
||||
self.server.query('/%s/analyze' % self.key.lstrip('/'), method=self.server.session.put)
|
||||
|
||||
def markWatched(self):
|
||||
"""Mark a items as watched."""
|
||||
|
@ -141,6 +141,39 @@ class Movie(Video, Playable):
|
|||
def isWatched(self):
|
||||
return bool(self.viewCount > 0)
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
""" This does not exist in plex xml response but is added to have a common
|
||||
interface to get the location of the Movie/Show/Episode
|
||||
|
||||
"""
|
||||
files = [i.file for i in self.iterParts() if i]
|
||||
if len(files) == 1:
|
||||
files = files[0]
|
||||
|
||||
return files
|
||||
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
downloaded = []
|
||||
locs = [i for i in self.iterParts() if i]
|
||||
for loc in locs:
|
||||
if keep_orginal_name is False:
|
||||
name = '%s.%s' % (self.title.replace(' ', '.'), loc.container)
|
||||
else:
|
||||
name = loc.file
|
||||
|
||||
# So this seems to be a alot slower but allows transcode.
|
||||
if kwargs:
|
||||
download_url = self.getStreamURL(**kwargs)
|
||||
else:
|
||||
download_url = self.server.url('%s?download=1' % loc.key)
|
||||
|
||||
dl = utils.download(download_url, filename=name, savepath=savepath, session=self.server.session)
|
||||
if dl:
|
||||
downloaded.append(dl)
|
||||
|
||||
return downloaded
|
||||
|
||||
|
||||
@utils.register_libtype
|
||||
class Show(Video):
|
||||
|
@ -153,6 +186,8 @@ class Show(Video):
|
|||
data (Element): Usually built from server.query
|
||||
"""
|
||||
Video._loadData(self, data)
|
||||
# Incase this was loaded from search etc
|
||||
self.key = self.key.replace('/children', '')
|
||||
self.art = data.attrib.get('art', NA)
|
||||
self.banner = data.attrib.get('banner', NA)
|
||||
self.childCount = utils.cast(int, data.attrib.get('childCount', NA))
|
||||
|
@ -161,7 +196,7 @@ class Show(Video):
|
|||
self.guid = data.attrib.get('guid', NA)
|
||||
self.index = data.attrib.get('index', NA)
|
||||
self.leafCount = utils.cast(int, data.attrib.get('leafCount', NA))
|
||||
self.location = utils.findLocations(data, single=True)
|
||||
self.location = utils.findLocations(data, single=True) or NA
|
||||
self.originallyAvailableAt = utils.toDatetime(
|
||||
data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
|
||||
self.rating = utils.cast(float, data.attrib.get('rating', NA))
|
||||
|
@ -170,11 +205,11 @@ class Show(Video):
|
|||
self.viewedLeafCount = utils.cast(
|
||||
int, data.attrib.get('viewedLeafCount', NA))
|
||||
self.year = utils.cast(int, data.attrib.get('year', NA))
|
||||
#if self.isFullObject(): # will be fixed with docs.
|
||||
self.genres = [media.Genre(self.server, e)
|
||||
for e in data if e.tag == media.Genre.TYPE]
|
||||
self.roles = [media.Role(self.server, e)
|
||||
for e in data if e.tag == media.Role.TYPE]
|
||||
if self.isFullObject(): # will be fixed with docs.
|
||||
self.genres = [media.Genre(self.server, e)
|
||||
for e in data if e.tag == media.Genre.TYPE]
|
||||
self.roles = [media.Role(self.server, e)
|
||||
for e in data if e.tag == media.Role.TYPE]
|
||||
|
||||
@property
|
||||
def actors(self):
|
||||
|
@ -266,9 +301,23 @@ class Show(Video):
|
|||
"""
|
||||
return self.episode(title)
|
||||
|
||||
def analyze(self):
|
||||
""" """
|
||||
raise 'Cant analyse a show' # fix me
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh the metadata."""
|
||||
self.server.query('/library/metadata/%s/refresh' % self.ratingKey)
|
||||
self.server.query('/library/metadata/%s/refresh' % self.ratingKey, method=self.server.session.put)
|
||||
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
downloaded = []
|
||||
for ep in self.episodes():
|
||||
dl = ep.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
|
||||
|
||||
if dl:
|
||||
downloaded.extend(dl)
|
||||
|
||||
return downloaded
|
||||
|
||||
|
||||
@utils.register_libtype
|
||||
|
@ -282,11 +331,12 @@ class Season(Video):
|
|||
data (Element): Usually built from server.query
|
||||
"""
|
||||
Video._loadData(self, data)
|
||||
self.key = self.key.replace('/children', '')
|
||||
self.leafCount = utils.cast(int, data.attrib.get('leafCount', NA))
|
||||
self.index = utils.cast(int, data.attrib.get('index', NA))
|
||||
self.parentKey = data.attrib.get('parentKey', NA)
|
||||
self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey', NA))
|
||||
self.grandparentTitle = data.attrib.get('grandparentTitle', NA)
|
||||
self.parentTitle = data.attrib.get('parentTitle', NA)
|
||||
self.viewedLeafCount = utils.cast(
|
||||
int, data.attrib.get('viewedLeafCount', NA))
|
||||
|
||||
|
@ -296,7 +346,7 @@ class Season(Video):
|
|||
|
||||
@property
|
||||
def seasonNumber(self):
|
||||
"""Reurns season number."""
|
||||
"""Returns season number."""
|
||||
return self.index
|
||||
|
||||
def episodes(self, watched=None):
|
||||
|
@ -352,7 +402,6 @@ class Season(Video):
|
|||
else:
|
||||
raise NotFound('Couldnt find %s.Season %s Episode %s.' % (self.grandparentTitle, self.index. episode))
|
||||
|
||||
|
||||
def get(self, title):
|
||||
"""Get a episode with a matching title.
|
||||
|
||||
|
@ -380,7 +429,16 @@ class Season(Video):
|
|||
clsname = self.__class__.__name__
|
||||
key = self.key.replace('/library/metadata/', '').replace('/children', '') if self.key else 'NA'
|
||||
title = self.title.replace(' ', '.')[0:20].encode('utf8')
|
||||
return '<%s:%s:%s:%s>' % (clsname, key, self.grandparentTitle, title)
|
||||
return '<%s:%s:%s:%s>' % (clsname, key, self.parentTitle, title)
|
||||
|
||||
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
||||
downloaded = []
|
||||
for ep in self.episodes():
|
||||
dl = ep.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
|
||||
if dl:
|
||||
downloaded.extend(dl)
|
||||
|
||||
return downloaded
|
||||
|
||||
|
||||
@utils.register_libtype
|
||||
|
@ -464,3 +522,19 @@ class Episode(Video, Playable):
|
|||
def show(self):
|
||||
"""Return this episodes Show"""
|
||||
return utils.listItems(self.server, self.grandparentKey)[0]
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
""" This does not exist in plex xml response but is added to have a common
|
||||
interface to get the location of the Movie/Show
|
||||
|
||||
"""
|
||||
# Note this should probably belong to some parent.
|
||||
files = [i.file for i in self.iterParts() if i]
|
||||
if len(files) == 1:
|
||||
files = files[0]
|
||||
|
||||
return files
|
||||
|
||||
def _prettyfilename(self):
|
||||
return '%s.S%sE%s' % (self.grandparentTitle.replace(' ', '.'), str(self.seasonNumber).zfill(2), str(self.index).zfill(2))
|
||||
|
|
7
requirements_dev.txt
Normal file
7
requirements_dev.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
requests
|
||||
pytest
|
||||
pytest-cov
|
||||
betamax
|
||||
betamax_serializers
|
||||
pillow
|
||||
coveralls
|
7
tests/tests_pytest/__init__.py
Normal file
7
tests/tests_pytest/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
|
||||
plexapi_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
sys.path.insert(0, plexapi_path)
|
163
tests/tests_pytest/conftest.py
Normal file
163
tests/tests_pytest/conftest.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
from functools import partial
|
||||
import os
|
||||
import betamax
|
||||
from betamax_serializers import pretty_json
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
import plexapi
|
||||
|
||||
token = os.environ.get('PLEX_TOKEN')
|
||||
test_token = os.environ.get('PLEX_TEST_TOKEN')
|
||||
test_username = os.environ.get('PLEX_TEST_USERNAME')
|
||||
test_password = os.environ.get('PLEX_TEST_PASSWORD')
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def pms(request):
|
||||
from plexapi.server import PlexServer
|
||||
|
||||
sess = requests.Session()
|
||||
|
||||
"""
|
||||
CASSETTE_LIBRARY_DIR = 'response/'
|
||||
|
||||
betamax.Betamax.register_serializer(pretty_json.PrettyJSONSerializer)
|
||||
config = betamax.Betamax.configure()
|
||||
config.define_cassette_placeholder('MASKED', token)
|
||||
config.define_cassette_placeholder('MASKED', test_token)
|
||||
|
||||
recorder = betamax.Betamax(sess, cassette_library_dir=CASSETTE_LIBRARY_DIR)
|
||||
recorder.use_cassette('http_responses', serialize_with='prettyjson') # record='new_episodes'
|
||||
recorder.start()
|
||||
"""
|
||||
url = 'http://138.68.157.5:32400'
|
||||
assert test_token
|
||||
assert url
|
||||
|
||||
pms = PlexServer(url, test_token, session=sess)
|
||||
#request.addfinalizer(recorder.stop)
|
||||
return pms
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def freshpms():
|
||||
from plexapi.server import PlexServer
|
||||
|
||||
sess = requests.Session()
|
||||
|
||||
url = 'http://138.68.157.5:32400'
|
||||
assert test_token
|
||||
assert url
|
||||
|
||||
pms = PlexServer(url, test_token, session=sess)
|
||||
return pms
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--req_client", action="store_true",
|
||||
help="Run tests that interact with a client")
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if 'req_client' in item.keywords and not item.config.getvalue("req_client"):
|
||||
pytest.skip("need --req_client option to run")
|
||||
else:
|
||||
item.config.getvalue("req_client")
|
||||
|
||||
@pytest.fixture()
|
||||
def plex_account():
|
||||
from plexapi.myplex import MyPlexAccount
|
||||
username = test_username
|
||||
password = test_password
|
||||
assert username and password
|
||||
account = MyPlexAccount.signin(username, password)
|
||||
assert account
|
||||
return account
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_movie(pms):
|
||||
m = pms.library.search('16 blocks')
|
||||
assert m
|
||||
return m[0]
|
||||
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_tv_section(pms):
|
||||
sec = pms.library.section('TV Shows')
|
||||
assert sec
|
||||
return sec
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_movie_section(pms):
|
||||
sec = pms.library.section('Movies')
|
||||
assert sec
|
||||
return sec
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_music_section(pms):
|
||||
sec = pms.library.section('Music')
|
||||
assert sec
|
||||
return sec
|
||||
|
||||
@pytest.fixture()
|
||||
def a_photo_section(pms):
|
||||
sec = pms.library.section('Photos')
|
||||
assert sec
|
||||
return sec
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_artist(a_music_section):
|
||||
sec = a_music_section.get('Infinite State')
|
||||
assert sec
|
||||
return sec
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_music_album(a_music_section):
|
||||
sec = a_music_section.get('Infinite State').album('Unmastered Impulses')
|
||||
assert sec
|
||||
return sec
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_track(a_music_album):
|
||||
track = a_music_album.track('Holy Moment')
|
||||
assert track
|
||||
return track
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_show(a_tv_section):
|
||||
sec = a_tv_section.get('The 100')
|
||||
assert sec
|
||||
return sec
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_episode(a_show):
|
||||
ep = a_show.get('Pilot')
|
||||
assert ep
|
||||
return ep
|
||||
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_photo_album(pms):
|
||||
sec = pms.library.section('Photos')
|
||||
assert sec
|
||||
album = sec.get('photo_album1')
|
||||
assert album
|
||||
return album
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def monkeydownload(request, monkeypatch):
|
||||
monkeypatch.setattr('plexapi.utils.download', partial(plexapi.utils.download, mocked=True))
|
||||
yield
|
||||
monkeypatch.undo()
|
20
tests/tests_pytest/test_actions.py
Normal file
20
tests/tests_pytest/test_actions.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
def test_mark_movie_watched(a_movie):
|
||||
a_movie.markUnwatched()
|
||||
print('Marking movie watched: %s' % a_movie)
|
||||
print('View count: %s' % a_movie.viewCount)
|
||||
a_movie.markWatched()
|
||||
print('View count: %s' % a_movie.viewCount)
|
||||
assert a_movie.viewCount == 1, 'View count 0 after watched.'
|
||||
a_movie.markUnwatched()
|
||||
print('View count: %s' % a_movie.viewCount)
|
||||
assert a_movie.viewCount == 0, 'View count 1 after unwatched.'
|
||||
|
||||
|
||||
def test_refresh_section(pms):
|
||||
shows = pms.library.section('TV Shows')
|
||||
#shows.refresh()
|
||||
|
||||
|
||||
def test_refresh_video(pms):
|
||||
result = pms.search('16 blocks')
|
||||
#result[0].refresh()
|
332
tests/tests_pytest/test_audio.py
Normal file
332
tests/tests_pytest/test_audio.py
Normal file
|
@ -0,0 +1,332 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_audio_Artist_attr(a_artist):
|
||||
m = a_artist
|
||||
m.reload()
|
||||
assert str(m.addedAt.date()) == '2017-01-17'
|
||||
assert m.countries == []
|
||||
assert [i.tag for i in m.genres] == ['Electronic']
|
||||
assert m.guid == 'com.plexapp.agents.lastfm://Infinite%20State?lang=en'
|
||||
assert m.index == '1'
|
||||
assert m.initpath == '/library/metadata/20'
|
||||
assert m.key == '/library/metadata/20'
|
||||
assert m.librarySectionID == '3'
|
||||
assert m.listType == 'audio'
|
||||
assert m.location == '/media/music/unmastered_impulses'
|
||||
assert m.ratingKey == 20
|
||||
assert m.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert m.similar == []
|
||||
assert m.summary == ""
|
||||
assert m.title == 'Infinite State'
|
||||
assert m.titleSort == 'Infinite State'
|
||||
assert m.type == 'artist'
|
||||
assert str(m.updatedAt.date()) == '2017-01-25'
|
||||
assert m.viewCount == 0
|
||||
|
||||
|
||||
def test_audio_Artist_get(a_artist, a_music_section):
|
||||
a_artist == a_music_section.searchArtists(**{'title': 'Infinite State'})[0]
|
||||
a_artist.title == 'Infinite State'
|
||||
|
||||
|
||||
def test_audio_Artist_track(a_artist):
|
||||
track = a_artist.track('Holy Moment')
|
||||
assert track.title == 'Holy Moment'
|
||||
|
||||
|
||||
def test_audio_Artist_tracks(a_artist):
|
||||
tracks = a_artist.tracks()
|
||||
assert len(tracks) == 14
|
||||
|
||||
|
||||
def test_audio_Artist_album(a_artist):
|
||||
album = a_artist.album('Unmastered Impulses')
|
||||
assert album.title == 'Unmastered Impulses'
|
||||
|
||||
|
||||
def test_audio_Artist_albums(a_artist):
|
||||
albums = a_artist.albums()
|
||||
assert len(albums) == 1 and albums[0].title == 'Unmastered Impulses'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test_audio_Album_attrs(a_music_album):
|
||||
m = a_music_album
|
||||
assert str(m.addedAt.date()) == '2017-01-17'
|
||||
assert [i.tag for i in m.genres] == ['Electronic']
|
||||
assert m.index == '1'
|
||||
assert m.initpath == '/library/metadata/21'
|
||||
assert m.key == '/library/metadata/21'
|
||||
assert m.librarySectionID == '3'
|
||||
assert m.listType == 'audio'
|
||||
assert str(m.originallyAvailableAt.date()) == '2016-01-01'
|
||||
assert m.parentKey == '/library/metadata/20'
|
||||
assert m.parentRatingKey == '20'
|
||||
assert str(m.parentThumb) == '__NA__'
|
||||
assert m.parentTitle == 'Infinite State'
|
||||
assert m.ratingKey == 21
|
||||
assert m.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert str(m.studio) == '__NA__'
|
||||
assert m.summary == ''
|
||||
assert m.thumb == '/library/metadata/21/thumb/1484693407'
|
||||
assert m.title == 'Unmastered Impulses'
|
||||
assert m.titleSort == 'Unmastered Impulses'
|
||||
assert m.type == 'album'
|
||||
assert str(m.updatedAt.date()) == '2017-01-17'
|
||||
assert m.viewCount == 0
|
||||
assert m.year == 2016
|
||||
|
||||
|
||||
def test_audio_Album_tracks(a_music_album):
|
||||
tracks = a_music_album.tracks()
|
||||
assert len(tracks) == 14
|
||||
assert tracks[0].grandparentKey == '/library/metadata/20'
|
||||
assert tracks[0].grandparentRatingKey == '20'
|
||||
assert tracks[0].grandparentTitle == 'Infinite State'
|
||||
assert tracks[0].index == '1'
|
||||
assert tracks[0].initpath == '/library/metadata/21/children'
|
||||
assert tracks[0].key == '/library/metadata/22'
|
||||
assert tracks[0].listType == 'audio'
|
||||
assert tracks[0].originalTitle == 'Kenneth Reitz'
|
||||
assert tracks[0].parentIndex == '1'
|
||||
assert tracks[0].parentKey == '/library/metadata/21'
|
||||
assert tracks[0].parentRatingKey == '21'
|
||||
assert tracks[0].parentThumb == '/library/metadata/21/thumb/1484693407'
|
||||
assert tracks[0].parentTitle == 'Unmastered Impulses'
|
||||
assert tracks[0].player is None
|
||||
assert tracks[0].ratingCount == 9
|
||||
assert tracks[0].ratingKey == 22
|
||||
assert tracks[0].server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert tracks[0].summary == ""
|
||||
assert tracks[0].thumb == '/library/metadata/21/thumb/1484693407'
|
||||
assert tracks[0].title == 'Holy Moment'
|
||||
assert tracks[0].titleSort == 'Holy Moment'
|
||||
assert tracks[0].transcodeSession is None
|
||||
assert tracks[0].type == 'track'
|
||||
assert str(tracks[0].updatedAt.date()) == '2017-01-17'
|
||||
assert tracks[0].username is None
|
||||
assert tracks[0].viewCount == 0
|
||||
assert tracks[0].viewOffset == 0
|
||||
|
||||
|
||||
|
||||
def test_audio_Album_track(a_music_album):
|
||||
# this is not reloaded. its not that much info missing.
|
||||
track = a_music_album.track('Holy Moment')
|
||||
|
||||
assert str(track.addedAt.date()) == '2017-01-17'
|
||||
assert track.duration == 298606
|
||||
assert track.grandparentKey == '/library/metadata/20'
|
||||
assert track.grandparentRatingKey == '20'
|
||||
assert track.grandparentTitle == 'Infinite State'
|
||||
assert track.index == '1'
|
||||
assert track.initpath == '/library/metadata/21/children'
|
||||
assert track.key == '/library/metadata/22'
|
||||
assert track.listType == 'audio'
|
||||
# Assign 0 track.media
|
||||
med0 = track.media[0]
|
||||
assert track.originalTitle == 'Kenneth Reitz'
|
||||
assert track.parentIndex == '1'
|
||||
assert track.parentKey == '/library/metadata/21'
|
||||
assert track.parentRatingKey == '21'
|
||||
assert track.parentThumb == '/library/metadata/21/thumb/1484693407'
|
||||
assert track.parentTitle == 'Unmastered Impulses'
|
||||
assert track.player is None
|
||||
assert track.ratingCount == 9
|
||||
assert track.ratingKey == 22
|
||||
assert track.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert track.summary == ''
|
||||
assert track.thumb == '/library/metadata/21/thumb/1484693407'
|
||||
assert track.title == 'Holy Moment'
|
||||
assert track.titleSort == 'Holy Moment'
|
||||
assert track.transcodeSession is None
|
||||
assert track.type == 'track'
|
||||
assert str(track.updatedAt.date()) == '2017-01-17'
|
||||
assert track.username is None
|
||||
assert track.viewCount == 0
|
||||
assert track.viewOffset == 0
|
||||
assert med0.aspectRatio is None
|
||||
assert med0.audioChannels == 2
|
||||
assert med0.audioCodec == 'mp3'
|
||||
assert med0.bitrate == 385
|
||||
assert med0.container == 'mp3'
|
||||
assert med0.duration == 298606
|
||||
assert med0.height is None
|
||||
assert med0.id == 22
|
||||
assert med0.initpath == '/library/metadata/21/children'
|
||||
assert med0.optimizedForStreaming is None
|
||||
# Assign 0 med0.parts
|
||||
par0 = med0.parts[0]
|
||||
assert med0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert med0.videoCodec is None
|
||||
assert med0.videoFrameRate is None
|
||||
assert med0.videoResolution is None
|
||||
assert med0.width is None
|
||||
assert par0.container == 'mp3'
|
||||
assert par0.duration == 298606
|
||||
assert par0.file == '/media/music/unmastered_impulses/01-Holy_Moment.mp3'
|
||||
assert par0.id == 22
|
||||
assert par0.initpath == '/library/metadata/21/children'
|
||||
assert par0.key == '/library/parts/22/1484693136/file.mp3'
|
||||
assert par0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert par0.size == 14360402
|
||||
|
||||
|
||||
|
||||
|
||||
def test_audio_Album_get():
|
||||
""" just a alias for track, lets skip it"""
|
||||
pass
|
||||
|
||||
def test_audio_Album_artist(a_music_album):
|
||||
artist = a_music_album.artist()
|
||||
artist.title == 'Infinite State'
|
||||
|
||||
|
||||
def test_audio_Track_attrs(a_music_album):
|
||||
track = a_music_album.get('Holy Moment')
|
||||
track.reload()
|
||||
|
||||
assert str(track.addedAt.date()) == '2017-01-17'
|
||||
assert str(track.art) == '__NA__'
|
||||
assert str(track.chapterSource) == '__NA__'
|
||||
assert track.duration == 298606
|
||||
assert str(track.grandparentArt) == '__NA__'
|
||||
assert track.grandparentKey == '/library/metadata/20'
|
||||
assert track.grandparentRatingKey == '20'
|
||||
assert str(track.grandparentThumb) == '__NA__'
|
||||
assert track.grandparentTitle == 'Infinite State'
|
||||
assert track.guid == 'local://22'
|
||||
assert track.index == '1'
|
||||
assert track.initpath == '/library/metadata/22'
|
||||
assert track.key == '/library/metadata/22'
|
||||
assert str(track.lastViewedAt) == '__NA__'
|
||||
assert track.librarySectionID == '3'
|
||||
assert track.listType == 'audio'
|
||||
# Assign 0 track.media
|
||||
med0 = track.media[0]
|
||||
assert track.moods == []
|
||||
assert track.originalTitle == 'Kenneth Reitz'
|
||||
assert track.parentIndex == '1'
|
||||
assert track.parentKey == '/library/metadata/21'
|
||||
assert track.parentRatingKey == '21'
|
||||
assert track.parentThumb == '/library/metadata/21/thumb/1484693407'
|
||||
assert track.parentTitle == 'Unmastered Impulses'
|
||||
assert track.player is None
|
||||
assert str(track.playlistItemID) == '__NA__'
|
||||
assert str(track.primaryExtraKey) == '__NA__'
|
||||
assert track.ratingCount == 9
|
||||
assert track.ratingKey == 22
|
||||
assert track.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert str(track.sessionKey) == '__NA__'
|
||||
assert track.summary == ''
|
||||
assert track.thumb == '/library/metadata/21/thumb/1484693407'
|
||||
assert track.title == 'Holy Moment'
|
||||
assert track.titleSort == 'Holy Moment'
|
||||
assert track.transcodeSession is None
|
||||
assert track.type == 'track'
|
||||
assert str(track.updatedAt.date()) == '2017-01-17'
|
||||
assert track.username is None
|
||||
assert track.viewCount == 0
|
||||
assert track.viewOffset == 0
|
||||
assert str(track.viewedAt) == '__NA__'
|
||||
assert str(track.year) == '__NA__'
|
||||
assert med0.aspectRatio is None
|
||||
assert med0.audioChannels == 2
|
||||
assert med0.audioCodec == 'mp3'
|
||||
assert med0.bitrate == 385
|
||||
assert med0.container == 'mp3'
|
||||
assert med0.duration == 298606
|
||||
assert med0.height is None
|
||||
assert med0.id == 22
|
||||
assert med0.initpath == '/library/metadata/22'
|
||||
assert med0.optimizedForStreaming is None
|
||||
# Assign 0 med0.parts
|
||||
par0 = med0.parts[0]
|
||||
assert med0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert med0.videoCodec is None
|
||||
assert med0.videoFrameRate is None
|
||||
assert med0.videoResolution is None
|
||||
assert med0.width is None
|
||||
assert par0.container == 'mp3'
|
||||
assert par0.duration == 298606
|
||||
assert par0.file == '/media/music/unmastered_impulses/01-Holy_Moment.mp3'
|
||||
assert par0.id == 22
|
||||
assert par0.initpath == '/library/metadata/22'
|
||||
assert par0.key == '/library/parts/22/1484693136/file.mp3'
|
||||
#assert par0.media == <Media:Holy.Moment>
|
||||
assert par0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert par0.size == 14360402
|
||||
# Assign 0 par0.streams
|
||||
str0 = par0.streams[0]
|
||||
assert str0.audioChannelLayout == 'stereo'
|
||||
assert str0.bitDepth is None
|
||||
assert str0.bitrate == 320
|
||||
assert str0.bitrateMode is None
|
||||
assert str0.channels == 2
|
||||
assert str0.codec == 'mp3'
|
||||
assert str0.codecID is None
|
||||
assert str0.dialogNorm is None
|
||||
assert str0.duration is None
|
||||
assert str0.id == 44
|
||||
assert str0.index == 0
|
||||
assert str0.initpath == '/library/metadata/22'
|
||||
assert str0.language is None
|
||||
assert str0.languageCode is None
|
||||
#assert str0.part == <MediaPart:22>
|
||||
assert str0.samplingRate == 44100
|
||||
assert str0.selected is True
|
||||
assert str0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert str0.streamType == 2
|
||||
assert str0.title is None
|
||||
assert str0.type == 2
|
||||
|
||||
|
||||
|
||||
def test_audio_Track_album(a_music_album):
|
||||
assert a_music_album.tracks()[0].album() == a_music_album
|
||||
|
||||
def test_audio_Track_artist(a_music_album, a_artist):
|
||||
assert a_music_album.tracks()[0].artist() == a_artist
|
||||
|
||||
|
||||
def test_audio_Audio_section(a_artist, a_music_album, a_track):
|
||||
assert a_artist.section()
|
||||
assert a_music_album.section()
|
||||
assert a_track.section()
|
||||
|
||||
assert a_track.section() == a_music_album.section() == a_artist.section()
|
||||
|
||||
|
||||
def test_audio_Track_download(monkeydownload, tmpdir, a_track):
|
||||
f = a_track.download(savepath=str(tmpdir))
|
||||
assert f
|
||||
|
||||
|
||||
def test_audio_album_download(monkeydownload, a_music_album, tmpdir):
|
||||
f = a_music_album.download(savepath=str(tmpdir))
|
||||
assert len(f) == 14
|
||||
|
||||
|
||||
def test_audio_Artist_download(monkeydownload, a_artist, tmpdir):
|
||||
f = a_artist.download(savepath=str(tmpdir))
|
||||
assert len(f) == 14
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
173
tests/tests_pytest/test_client.py
Normal file
173
tests/tests_pytest/test_client.py
Normal file
|
@ -0,0 +1,173 @@
|
|||
import pytest
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient__loadData(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_connect(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_contextMenu(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_goBack(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_goToHome(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_goToMedia(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_goToMusic(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_headers(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_isPlayingMedia(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_moveDown(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_moveLeft(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_moveRight(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_moveUp(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_nextLetter(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_pageDown(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_pageUp(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_pause(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_play(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_playMedia(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_previousLetter(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_proxyThroughServer(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_query(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_refreshPlayQueue(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_seekTo(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_select(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_sendCommand(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_setAudioStream(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_setParameters(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_setRepeat(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_setShuffle(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_setStreams(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_setSubtitleStream(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_setVideoStream(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_setVolume(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_skipNext(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_skipPrevious(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_skipTo(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_stepBack(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_stepForward(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_stop(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_timeline(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_toggleOSD(pms):
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_client_PlexClient_url(pms):
|
||||
pass
|
175
tests/tests_pytest/test_library.py
Normal file
175
tests/tests_pytest/test_library.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
from plexapi.exceptions import NotFound
|
||||
|
||||
# func naming should follow:
|
||||
# tests_file_class_method_some_description
|
||||
|
||||
|
||||
def test_library_Library_section(pms):
|
||||
sections = pms.library.sections()
|
||||
assert len(sections) == 4
|
||||
|
||||
lfs = 'TV Shows'
|
||||
section_name = pms.library.section(lfs)
|
||||
assert section_name.title == lfs
|
||||
|
||||
with pytest.raises(NotFound):
|
||||
assert pms.library.section('gfdsas')
|
||||
|
||||
|
||||
def test_library_Library_sectionByID_is_equal_section(pms, freshpms):
|
||||
# test that sctionmyID refreshes the section if the key is missing
|
||||
# this is needed if there isnt any cached sections
|
||||
assert freshpms.library.sectionByID('1')
|
||||
|
||||
assert pms.library.sectionByID('1').uuid == pms.library.section('Movies').uuid
|
||||
|
||||
|
||||
|
||||
def test_library_sectionByID_with_attrs(pms):
|
||||
m = pms.library.sectionByID('1')
|
||||
assert m.agent == 'com.plexapp.agents.imdb'
|
||||
assert m.allowSync is False
|
||||
assert m.art == '/:/resources/movie-fanart.jpg'
|
||||
assert m.composite == '/library/sections/1/composite/1484690696'
|
||||
assert str(m.createdAt.date()) == '2017-01-17'
|
||||
assert m.filters == '1'
|
||||
assert m.initpath == '/library/sections'
|
||||
assert m.key == '1'
|
||||
assert m.language == 'en'
|
||||
assert m.locations == ['/media/movies']
|
||||
assert m.refreshing is False
|
||||
assert m.scanner == 'Plex Movie Scanner'
|
||||
assert m.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert m.thumb == '/:/resources/movie.png'
|
||||
assert m.title == 'Movies'
|
||||
assert m.type == 'movie'
|
||||
assert str(m.updatedAt.date()) == '2017-01-17'
|
||||
assert m.uuid == '2b72d593-3881-43f4-a8b8-db541bd3535a'
|
||||
|
||||
|
||||
def test_library_section_get_movie(pms): # fix me
|
||||
m = pms.library.section('Movies').get('16 blocks')
|
||||
assert m
|
||||
|
||||
|
||||
def test_library_getByKey(pms):
|
||||
m = pms.library.getByKey('1')
|
||||
assert m.title == '16 Blocks'
|
||||
|
||||
|
||||
def test_library_onDeck(pms):
|
||||
assert len(list(pms.library.onDeck()))
|
||||
|
||||
|
||||
def test_library_recentlyAdded(pms):
|
||||
assert len(list(pms.library.recentlyAdded()))
|
||||
|
||||
|
||||
def test_library_get(pms):
|
||||
m = pms.library.get('16 blocks')
|
||||
assert m.title == '16 Blocks'
|
||||
|
||||
def test_library_Library_cleanBundle(pms):
|
||||
pms.library.cleanBundles()
|
||||
|
||||
|
||||
def test_library_Library_optimize(pms):
|
||||
pms.library.optimize()
|
||||
|
||||
def test_library_Library_emptyTrash(pms):
|
||||
pms.library.emptyTrash()
|
||||
|
||||
def _test_library_Library_refresh(pms):
|
||||
pms.library.refresh() # fix mangle and proof the sections attrs
|
||||
|
||||
|
||||
def _test_library_MovieSection_refresh(a_movie_section):
|
||||
a_movie_section.refresh()
|
||||
|
||||
|
||||
def test_library_MovieSection_onDeck(a_movie_section):
|
||||
assert len(a_movie_section.onDeck())
|
||||
|
||||
|
||||
def test_library_MovieSection_recentlyAdded(a_movie_section):
|
||||
assert len(a_movie_section.recentlyAdded())
|
||||
|
||||
|
||||
def test_library_MovieSection_analyze(a_movie_section):
|
||||
a_movie_section.analyze()
|
||||
|
||||
|
||||
def test_library_ShowSection_searchShows(a_tv_section):
|
||||
s = a_tv_section.searchShows(**{'title': 'The 100'})
|
||||
assert s
|
||||
|
||||
|
||||
def test_library_ShowSection_searchEpisodes(a_tv_section):
|
||||
s = a_tv_section.searchEpisodes(**{'title': 'Pilot'})
|
||||
assert s
|
||||
|
||||
|
||||
def test_library_ShowSection_recentlyAdded(a_tv_section):
|
||||
assert len(a_tv_section.recentlyAdded())
|
||||
|
||||
|
||||
def test_library_MusicSection_albums(a_music_section):
|
||||
assert len(a_music_section.albums())
|
||||
|
||||
|
||||
def test_library_MusicSection_searchTracks(a_music_section):
|
||||
assert len(a_music_section.searchTracks(**{'title': 'Holy Moment'}))
|
||||
|
||||
|
||||
def test_library_MusicSection_searchAlbums(a_music_section):
|
||||
assert len(a_music_section.searchAlbums(**{'title': 'Unmastered Impulses'}))
|
||||
|
||||
|
||||
def test_library_PhotoSection_searchAlbums(a_photo_section):
|
||||
albums = a_photo_section.searchAlbums('photo_album1')
|
||||
assert len(albums)
|
||||
print([i.TYPE for i in albums])
|
||||
|
||||
|
||||
|
||||
def test_library_PhotoSection_searchPhotos(a_photo_section):
|
||||
assert len(a_photo_section.searchPhotos('lolcat2'))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#### Start on library search
|
||||
|
||||
def test_library_and_section_search_for_movie(pms):
|
||||
find = '16 blocks'
|
||||
l_search = pms.library.search(find)
|
||||
s_search = pms.library.section('Movies').search(find)
|
||||
assert l_search == s_search
|
||||
|
||||
|
||||
def test_search_with_apostrophe(pms):
|
||||
show_title = "Marvel's Daredevil" # Test ' in show title
|
||||
result_server = pms.search(show_title)
|
||||
result_shows = pms.library.section('TV Shows').search(show_title)
|
||||
|
||||
assert result_server
|
||||
assert result_shows
|
||||
assert result_server == result_shows
|
||||
|
||||
|
||||
def test_crazy_search(pms, a_movie):
|
||||
movie = a_movie
|
||||
movies = pms.library.section('Movies')
|
||||
|
||||
assert movie in pms.library.search(genre=29, libtype='movie')
|
||||
assert movie in movies.search(actor=movie.actors[0], sort='titleSort'), 'Unable to search movie by actor.'
|
||||
assert movie in movies.search(director=movie.directors[0]), 'Unable to search movie by director.'
|
||||
assert movie in movies.search(year=['2006', '2007']), 'Unable to search movie by year.'
|
||||
assert movie not in movies.search(year=2007), 'Unable to filter movie by year.'
|
||||
assert movie in movies.search(actor=movie.actors[0].id)
|
78
tests/tests_pytest/test_myplex.py
Normal file
78
tests/tests_pytest/test_myplex.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
def test_myplex_accounts(plex_account, pms):
|
||||
account = plex_account
|
||||
assert account, 'Must specify username, password & resource to run this test.'
|
||||
print('MyPlexAccount:')
|
||||
print('username: %s' % account.username)
|
||||
#print('authenticationToken: %s' % account.authenticationToken)
|
||||
print('email: %s' % account.email)
|
||||
print('home: %s' % account.home)
|
||||
print('queueEmail: %s' % account.queueEmail)
|
||||
assert account.username, 'Account has no username'
|
||||
assert account.authenticationToken, 'Account has no authenticationToken'
|
||||
assert account.email, 'Account has no email'
|
||||
assert account.home is not None, 'Account has no home'
|
||||
assert account.queueEmail, 'Account has no queueEmail'
|
||||
account = pms.account()
|
||||
print('Local PlexServer.account():')
|
||||
print('username: %s' % account.username)
|
||||
print('authToken: %s' % account.authToken)
|
||||
print('signInState: %s' % account.signInState)
|
||||
assert account.username, 'Account has no username'
|
||||
assert account.authToken, 'Account has no authToken'
|
||||
assert account.signInState, 'Account has no signInState'
|
||||
|
||||
|
||||
def test_myplex_resources(plex_account):
|
||||
account = plex_account
|
||||
assert account, 'Must specify username, password & resource to run this test.'
|
||||
resources = account.resources()
|
||||
for resource in resources:
|
||||
name = resource.name or 'Unknown'
|
||||
connections = [c.uri for c in resource.connections]
|
||||
connections = ', '.join(connections) if connections else 'None'
|
||||
print('%s (%s): %s' % (name, resource.product, connections))
|
||||
assert resources, 'No resources found for account: %s' % account.name
|
||||
|
||||
|
||||
def test_myplex_connect_to_resource(plex_account):
|
||||
for resource in plex_account.resources():
|
||||
if resource.name == 'PMS_API_TEST_SERVER':
|
||||
break
|
||||
server = resource.connect()
|
||||
assert 'Ohno' in server.url('Ohno')
|
||||
assert server
|
||||
|
||||
|
||||
def test_myplex_devices(plex_account):
|
||||
account = plex_account
|
||||
devices = account.devices()
|
||||
for device in devices:
|
||||
name = device.name or 'Unknown'
|
||||
connections = ', '.join(device.connections) if device.connections else 'None'
|
||||
print('%s (%s): %s' % (name, device.product, connections))
|
||||
assert devices, 'No devices found for account: %s' % account.name
|
||||
|
||||
|
||||
#@pytest.mark.req_client # this need to be recorded?
|
||||
def _test_myplex_connect_to_device(plex_account):
|
||||
account = plex_account
|
||||
devices = account.devices()
|
||||
for device in devices:
|
||||
if device.name == 'some client name' and len(device.connections):
|
||||
break
|
||||
client = device.connect()
|
||||
assert client, 'Unable to connect to device'
|
||||
|
||||
|
||||
def test_myplex_users(plex_account):
|
||||
account = plex_account
|
||||
users = account.users()
|
||||
assert users, 'Found no users on account: %s' % account.name
|
||||
print('Found %s users.' % len(users))
|
||||
user = account.user('Hellowlol')
|
||||
print('Found user: %s' % user)
|
||||
assert user, 'Could not find user Hellowlol'
|
42
tests/tests_pytest/test_navigation.py
Normal file
42
tests/tests_pytest/test_navigation.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_navigate_around_show(plex_account, pms):
|
||||
show = pms.library.section('TV Shows').get('The 100')
|
||||
seasons = show.seasons()
|
||||
season = show.season('Season 1')
|
||||
|
||||
episodes = show.episodes()
|
||||
episode = show.episode('Pilot')
|
||||
|
||||
assert 'Season 1' in [s.title for s in seasons], 'Unable to list season:'
|
||||
assert 'Pilot' in [e.title for e in episodes], 'Unable to list episode:'
|
||||
assert show.season(1) == season
|
||||
assert show.episode('Pilot') == episode, 'Unable to get show episode:'
|
||||
assert season.episode('Pilot') == episode, 'Unable to get season episode:'
|
||||
assert season.show() == show, 'season.show() doesnt match expected show.'
|
||||
assert episode.show() == show, 'episode.show() doesnt match expected show.'
|
||||
assert episode.season() == season, 'episode.season() doesnt match expected season.'
|
||||
|
||||
|
||||
def _test_navigate_around_artist(plex_account, pms):
|
||||
artist = pms.library.section(CONFIG.audio_section).get(CONFIG.audio_artist)
|
||||
albums = artist.albums()
|
||||
album = artist.album(CONFIG.audio_album)
|
||||
tracks = artist.tracks()
|
||||
track = artist.track(CONFIG.audio_track)
|
||||
print('Navigating around artist: %s' % artist)
|
||||
print('Albums: %s...' % albums[:3])
|
||||
print('Album: %s' % album)
|
||||
print('Tracks: %s...' % tracks[:3])
|
||||
print('Track: %s' % track)
|
||||
assert CONFIG.audio_album in [a.title for a in albums], 'Unable to list album: %s' % CONFIG.audio_album
|
||||
assert CONFIG.audio_track in [e.title for e in tracks], 'Unable to list track: %s' % CONFIG.audio_track
|
||||
assert artist.album(CONFIG.audio_album) == album, 'Unable to get artist album: %s' % CONFIG.audio_album
|
||||
assert artist.track(CONFIG.audio_track) == track, 'Unable to get artist track: %s' % CONFIG.audio_track
|
||||
assert album.track(CONFIG.audio_track) == track, 'Unable to get album track: %s' % CONFIG.audio_track
|
||||
assert album.artist() == artist, 'album.artist() doesnt match expected artist.'
|
||||
assert track.artist() == artist, 'track.artist() doesnt match expected artist.'
|
||||
assert track.album() == album, 'track.album() doesnt match expected album.'
|
111
tests/tests_pytest/test_playlist.py
Normal file
111
tests/tests_pytest/test_playlist.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test_create_playlist(pms, a_show):
|
||||
# create the playlist
|
||||
title = 'test_create_playlist_a_show'
|
||||
#print('Creating playlist %s..' % title)
|
||||
episodes = a_show.episodes()
|
||||
playlist = pms.createPlaylist(title, episodes[:3])
|
||||
try:
|
||||
items = playlist.items()
|
||||
#log(4, 'Title: %s' % playlist.title)
|
||||
#log(4, 'Items: %s' % items)
|
||||
#log(4, 'Duration: %s min' % int(playlist.duration / 60000.0))
|
||||
assert playlist.title == title, 'Playlist not created successfully.'
|
||||
assert len(items) == 3, 'Playlist does not contain 3 items.'
|
||||
assert items[0].ratingKey == episodes[0].ratingKey, 'Items not in proper order [0a].'
|
||||
assert items[1].ratingKey == episodes[1].ratingKey, 'Items not in proper order [1a].'
|
||||
assert items[2].ratingKey == episodes[2].ratingKey, 'Items not in proper order [2a].'
|
||||
# move items around (b)
|
||||
#print('Testing move items..')
|
||||
playlist.moveItem(items[1])
|
||||
items = playlist.items()
|
||||
assert items[0].ratingKey == episodes[1].ratingKey, 'Items not in proper order [0b].'
|
||||
assert items[1].ratingKey == episodes[0].ratingKey, 'Items not in proper order [1b].'
|
||||
assert items[2].ratingKey == episodes[2].ratingKey, 'Items not in proper order [2b].'
|
||||
# move items around (c)
|
||||
playlist.moveItem(items[0], items[1])
|
||||
items = playlist.items()
|
||||
assert items[0].ratingKey == episodes[0].ratingKey, 'Items not in proper order [0c].'
|
||||
assert items[1].ratingKey == episodes[1].ratingKey, 'Items not in proper order [1c].'
|
||||
assert items[2].ratingKey == episodes[2].ratingKey, 'Items not in proper order [2c].'
|
||||
# add an item
|
||||
#print('Testing add item: %s' % episodes[3])
|
||||
playlist.addItems(episodes[3])
|
||||
items = playlist.items()
|
||||
#log(4, '4th Item: %s' % items[3])
|
||||
assert items[3].ratingKey == episodes[3].ratingKey, 'Missing added item: %s' % episodes[3]
|
||||
# add two items
|
||||
#print('Testing add item: %s' % episodes[4:6])
|
||||
playlist.addItems(episodes[4:6])
|
||||
items = playlist.items()
|
||||
#log(4, '5th+ Items: %s' % items[4:])
|
||||
assert items[4].ratingKey == episodes[4].ratingKey, 'Missing added item: %s' % episodes[4]
|
||||
assert items[5].ratingKey == episodes[5].ratingKey, 'Missing added item: %s' % episodes[5]
|
||||
assert len(items) == 6, 'Playlist should have 6 items, %s found' % len(items)
|
||||
# remove item
|
||||
toremove = items[3]
|
||||
#print('Testing remove item: %s' % toremove)
|
||||
playlist.removeItem(toremove)
|
||||
items = playlist.items()
|
||||
assert toremove not in items, 'Removed item still in playlist: %s' % items[3]
|
||||
assert len(items) == 5, 'Playlist should have 5 items, %s found' % len(items)
|
||||
finally:
|
||||
playlist.delete()
|
||||
|
||||
|
||||
@pytest.mark.req_client
|
||||
def test_playlist_play(pms):
|
||||
client = getclient(CONFIG.client, CONFIG.client_baseurl, plex)
|
||||
artist = plex.library.section(CONFIG.audio_section).get(CONFIG.audio_artist)
|
||||
album = artist.album(CONFIG.audio_album)
|
||||
pl_name = 'test_play_playlist'
|
||||
playlist = plex.createPlaylist(pl_name, album)
|
||||
try:
|
||||
#print('Playing playlist: %s' % playlist)
|
||||
client.playMedia(playlist); time.sleep(5)
|
||||
#print('stop..')
|
||||
client.stop('music'); time.sleep(1)
|
||||
finally:
|
||||
playlist.delete()
|
||||
|
||||
assert pl_name not in [i.title for i in pms.playlists()]
|
||||
|
||||
|
||||
def test_playlist_photos(pms, a_photo_album):
|
||||
album = a_photo_album
|
||||
photos = album.photos()
|
||||
pl_name = 'test_playlist_photos'
|
||||
playlist = pms.createPlaylist(pl_name, photos)
|
||||
|
||||
try:
|
||||
assert len(playlist.items()) == 4
|
||||
finally:
|
||||
playlist.delete()
|
||||
|
||||
assert pl_name not in [i.title for i in pms.playlists()]
|
||||
|
||||
|
||||
@pytest.mark.req_client
|
||||
def _test_play_photos(account, plex):
|
||||
client = getclient('iphone-mike', CONFIG.client_baseurl, plex)
|
||||
photosection = plex.library.section(CONFIG.photo_section)
|
||||
album = photosection.get(CONFIG.photo_album)
|
||||
photos = album.photos()
|
||||
for photo in photos[:4]:
|
||||
client.playMedia(photo)
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
def test_play_queues(pms):
|
||||
episode = pms.library.section('TV Shows').get('the 100').get('Pilot')
|
||||
playqueue = pms.createPlayQueue(episode)
|
||||
assert len(playqueue.items) == 1, 'No items in play queue.'
|
||||
assert playqueue.items[0].title == episode.title, 'Wrong show queued.'
|
||||
assert playqueue.playQueueID, 'Play queue ID not set.'
|
3
tests/tests_pytest/test_search.py
Normal file
3
tests/tests_pytest/test_search.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
# test search.
|
||||
|
||||
# Many more tests is for search later.
|
216
tests/tests_pytest/test_server.py
Normal file
216
tests/tests_pytest/test_server.py
Normal file
|
@ -0,0 +1,216 @@
|
|||
import pytest
|
||||
import os
|
||||
|
||||
from plexapi.exceptions import BadRequest, NotFound
|
||||
from plexapi.utils import download
|
||||
|
||||
|
||||
def test_server_attr(pms):
|
||||
assert pms.baseurl == 'http://138.68.157.5:32400'
|
||||
assert pms.friendlyName == 'PMS_API_TEST_SERVER'
|
||||
assert pms.machineIdentifier == 'e42470b5c527c7e5ebbdc017b5a32c8c683f6f8b'
|
||||
assert pms.myPlex is True
|
||||
assert pms.myPlexMappingState == 'mapped'
|
||||
assert pms.myPlexSigninState == 'ok'
|
||||
assert pms.myPlexSubscription == '0'
|
||||
assert pms.myPlexUsername == 'testplexapi@gmail.com'
|
||||
assert pms.platform == 'Linux'
|
||||
assert pms.platformVersion == '4.4.0-59-generic (#80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017)'
|
||||
#assert pms.session == <requests.sessions.Session object at 0x029A5E10>
|
||||
assert pms.token == os.environ.get('PLEX_TEST_TOKEN')
|
||||
assert pms.transcoderActiveVideoSessions == 0
|
||||
assert pms.updatedAt == 1484943666
|
||||
assert pms.version == '1.3.3.3148-b38628e'
|
||||
|
||||
|
||||
@pytest.mark.req_client
|
||||
def test_server_session():
|
||||
pass
|
||||
|
||||
|
||||
def test_server_library(pms):
|
||||
assert pms.library
|
||||
|
||||
|
||||
def test_server_url(pms):
|
||||
assert 'ohno' in pms.url('ohno')
|
||||
|
||||
|
||||
def test_server_transcodeImage(tmpdir, pms, a_show):
|
||||
# Ideally we should also test the black white but this has to do for now.
|
||||
height = 500
|
||||
width = 500
|
||||
img_url_resize = pms.transcodeImage(a_show.banner, height, width)
|
||||
gray = img_url_resize = pms.transcodeImage(a_show.banner, height, width, saturation=0)
|
||||
|
||||
resized_image = download(img_url_resize, savepath=str(tmpdir), filename='resize_image')
|
||||
org_image = download(a_show.server.url(a_show.banner), savepath=str(tmpdir), filename='org_image')
|
||||
gray_image = download(gray, savepath=str(tmpdir), filename='gray_image')
|
||||
|
||||
|
||||
from PIL import Image, ImageStat
|
||||
|
||||
with Image.open(resized_image) as im:
|
||||
assert width, height == im.size
|
||||
|
||||
with Image.open(org_image) as im:
|
||||
assert width, height != im.size
|
||||
|
||||
|
||||
|
||||
def detect_color_image(file, thumb_size=150, MSE_cutoff=22, adjust_color_bias=True):
|
||||
#from http://stackoverflow.com/questions/20068945/detect-if-image-is-color-grayscale-or-black-and-white-with-python-pil
|
||||
pil_img = Image.open(file)
|
||||
bands = pil_img.getbands()
|
||||
if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
|
||||
thumb = pil_img.resize((thumb_size, thumb_size))
|
||||
SSE, bias = 0, [0, 0, 0]
|
||||
if adjust_color_bias:
|
||||
bias = ImageStat.Stat(thumb).mean[:3]
|
||||
bias = [b - sum(bias) / 3 for b in bias]
|
||||
for pixel in thumb.getdata():
|
||||
mu = sum(pixel) / 3
|
||||
SSE += sum((pixel[i] - mu - bias[i]) * (pixel[i] - mu - bias[i]) for i in [0, 1, 2])
|
||||
MSE = float(SSE) / (thumb_size * thumb_size)
|
||||
if MSE <= MSE_cutoff:
|
||||
return 'grayscale'
|
||||
else:
|
||||
return 'color'
|
||||
elif len(bands) == 1:
|
||||
return 'blackandwhite'
|
||||
|
||||
assert detect_color_image(gray_image, thumb_size=150) == 'grayscale'
|
||||
|
||||
|
||||
|
||||
def test_server_search(pms):
|
||||
# basic search. see test_search.py
|
||||
assert pms.search('16 Blocks')
|
||||
|
||||
assert pms.search('16 blocks', mediatype='movie')
|
||||
|
||||
|
||||
def test_server_playlist(pms):
|
||||
pl = pms.playlist('some_playlist')
|
||||
assert pl.title == 'some_playlist'
|
||||
|
||||
with pytest.raises(NotFound):
|
||||
pms.playlist('124xxx11y')
|
||||
|
||||
|
||||
def test_server_playlists(pms):
|
||||
playlists = pms.playlists()
|
||||
assert len(playlists)
|
||||
|
||||
|
||||
def test_server_history(pms):
|
||||
history = pms.history()
|
||||
assert len(history)
|
||||
|
||||
|
||||
def test_server_Server_query(pms):
|
||||
assert pms.query('/')
|
||||
|
||||
from plexapi.server import PlexServer
|
||||
|
||||
with pytest.raises(BadRequest):
|
||||
assert pms.query('/asdasdsada/12123127/aaaa', headers={'random_headers': '1337'})
|
||||
|
||||
with pytest.raises(NotFound):
|
||||
# This is really requests.exceptions.HTTPError:
|
||||
# 401 Client Error: Unauthorized for url:
|
||||
PlexServer('http://138.68.157.5:32400', '1234')
|
||||
|
||||
|
||||
|
||||
def test_server_Server_session():
|
||||
from requests import Session
|
||||
from plexapi.server import PlexServer
|
||||
|
||||
class MySession(Session):
|
||||
def __init__(self):
|
||||
super(self.__class__, self).__init__()
|
||||
self.plexapi_session_test = True
|
||||
|
||||
plex = PlexServer('http://138.68.157.5:32400',
|
||||
os.environ.get('PLEX_TEST_TOKEN'),
|
||||
session=MySession())
|
||||
|
||||
assert hasattr(plex.session, 'plexapi_session_test')
|
||||
|
||||
pl = plex.playlists()
|
||||
assert hasattr(pl[0].server.session, 'plexapi_session_test')
|
||||
|
||||
# check client
|
||||
# check myplex.
|
||||
|
||||
|
||||
|
||||
def test_server_token_in_headers(pms):
|
||||
h = pms.headers()
|
||||
assert 'X-Plex-Token' in h and len(h['X-Plex-Token'])
|
||||
|
||||
|
||||
def _test_server_createPlayQueue():
|
||||
# see test_playlists.py
|
||||
pass
|
||||
|
||||
def _test_server_createPlaylist():
|
||||
# see test_playlists.py
|
||||
pass
|
||||
|
||||
|
||||
def test_server_client_not_found(pms):
|
||||
with pytest.raises(NotFound):
|
||||
pms.client('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
|
||||
|
||||
|
||||
@pytest.mark.req_client
|
||||
def test_server_client(pms):
|
||||
assert pms.client('Plex Web (Chrome)')
|
||||
|
||||
def test_server_Server_sessions(pms):
|
||||
assert len(pms.sessions()) == 0
|
||||
|
||||
|
||||
@pytest.mark.req_client
|
||||
def test_server_clients(pms):
|
||||
assert len(pms.clients())
|
||||
m = pms.clients()[0]
|
||||
assert m.baseurl == 'http://127.0.0.1:32400'
|
||||
assert m.device is None
|
||||
assert m.deviceClass == 'pc'
|
||||
assert m.machineIdentifier == '89hgkrbqxaxmf45o1q2949ru'
|
||||
assert m.model is None
|
||||
assert m.platform is None
|
||||
assert m.platformVersion is None
|
||||
assert m.product == 'Plex Web'
|
||||
assert m.protocol == 'plex'
|
||||
assert m.protocolCapabilities == ['timeline', 'playback', 'navigation', 'mirror', 'playqueues']
|
||||
assert m.protocolVersion == '1'
|
||||
assert m.server.baseurl == 'http://138.68.157.5:32400'
|
||||
#assert m.session == <requests.sessions.Session object at 0x02945E10>
|
||||
assert m.state is None
|
||||
assert m.title == 'Plex Web (Chrome)'
|
||||
assert m.token is None
|
||||
assert m.vendor is None
|
||||
assert m.version == '2.12.5'
|
||||
|
||||
|
||||
def test_server_account(pms):
|
||||
acc = pms.account()
|
||||
assert acc.authToken
|
||||
#assert acc.mappingError == 'publisherror' # this is missing from time to time.. why?
|
||||
assert acc.mappingErrorMessage is None
|
||||
assert acc.mappingState == 'mapped'
|
||||
assert acc.privateAddress == '138.68.157.5'
|
||||
assert acc.privatePort == '32400'
|
||||
assert acc.publicAddress == '138.68.157.5'
|
||||
assert acc.publicPort == '32400'
|
||||
assert acc.signInState == 'ok'
|
||||
assert acc.subscriptionActive == '0'
|
||||
assert acc.subscriptionFeatures is None
|
||||
assert acc.subscriptionState == 'Unknown'
|
||||
assert acc.username == 'testplexapi@gmail.com'
|
||||
|
||||
|
100
tests/tests_pytest/test_utils.py
Normal file
100
tests/tests_pytest/test_utils.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
import pytest
|
||||
|
||||
|
||||
import plexapi.utils as utils
|
||||
from plexapi.exceptions import NotFound
|
||||
|
||||
|
||||
def test_utils_toDatetime():
|
||||
assert str(utils.toDatetime('2006-03-03', format='%Y-%m-%d')) == '2006-03-03 00:00:00'
|
||||
assert str(utils.toDatetime('0'))[:-9] == '1970-01-01'
|
||||
# should this handle args as '0' # no need element attrs are strings.
|
||||
|
||||
def _test_utils_threaded():
|
||||
pass
|
||||
|
||||
def test_utils_searchType():
|
||||
st = utils.searchType('movie')
|
||||
assert st == 1
|
||||
|
||||
movie = utils.searchType(1)
|
||||
assert movie == '1'
|
||||
|
||||
with pytest.raises(NotFound):
|
||||
utils.searchType('kekekekeke')
|
||||
|
||||
|
||||
def _test_utils_listItems():
|
||||
pass
|
||||
|
||||
def _test_utils_listChoices(pms):
|
||||
pass
|
||||
|
||||
|
||||
def test_utils_joinArgs():
|
||||
test_dict = {'genre': 'action', 'type': 1337}
|
||||
assert utils.joinArgs(test_dict) == '?genre=action&type=1337'
|
||||
|
||||
|
||||
def test_utils_isInt():
|
||||
assert utils.isInt(1) is True
|
||||
assert utils.isInt('got_you') is False
|
||||
assert utils.isInt('1337') is True
|
||||
|
||||
def _test_utils_findUsername():
|
||||
pass
|
||||
|
||||
def _test_utils_findStreams():
|
||||
pass
|
||||
|
||||
def _test_utils_findPlayer():
|
||||
pass
|
||||
|
||||
|
||||
def _test_utils_findLocations():
|
||||
pass
|
||||
|
||||
def _test_utils_findItem():
|
||||
pass
|
||||
|
||||
|
||||
def test_utils_findKey(pms):
|
||||
with pytest.raises(NotFound):
|
||||
assert utils.findKey(pms, '9999999')
|
||||
|
||||
assert utils.findKey(pms, '1')
|
||||
|
||||
|
||||
def test_utils_cast():
|
||||
t_int_int = utils.cast(int, 1)
|
||||
t_int_str_int = utils.cast(int, '1')
|
||||
t_bool_str_int = utils.cast(bool, '1')
|
||||
t_bool_int = utils.cast(bool, 1)
|
||||
t_float_int = utils.cast(float, 1)
|
||||
t_float_float = utils.cast(float, 1)
|
||||
t_float_str = utils.cast(float, 'kek')
|
||||
assert t_int_int == 1 and isinstance(t_int_int, int)
|
||||
assert t_int_str_int == 1 and isinstance(t_int_str_int, int)
|
||||
assert t_bool_str_int is True
|
||||
assert t_bool_int is True
|
||||
assert t_float_float == 1.0 and isinstance(t_float_float, float)
|
||||
assert t_float_str != t_float_str # nan is never equal
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
t_bool_str = utils.cast(bool, 'kek') # should we catch this in cast?
|
||||
|
||||
|
||||
|
||||
def test_utils_download(a_episode):
|
||||
# this files is really getting downloaded..
|
||||
without_session = utils.download(a_episode.getStreamURL(), filename=a_episode.location, mocked=True)
|
||||
assert without_session
|
||||
|
||||
with_session = utils.download(a_episode.getStreamURL(),
|
||||
filename=a_episode.location,
|
||||
session=a_episode.server.session,
|
||||
mocked=True)
|
||||
assert with_session
|
||||
|
||||
img = utils.download(a_episode.thumbUrl, filename=a_episode.title, mocked=True)
|
||||
assert img
|
571
tests/tests_pytest/test_video.py
Normal file
571
tests/tests_pytest/test_video.py
Normal file
|
@ -0,0 +1,571 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#test_the_file_class_method
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from plexapi.exceptions import NotFound
|
||||
|
||||
|
||||
def test_video_Movie(a_movie_section):
|
||||
m = a_movie_section.get('Cars')
|
||||
assert m.title == 'Cars'
|
||||
|
||||
def test_video_Movie_getStreamURL(a_movie):
|
||||
assert a_movie.getStreamURL() == "http://138.68.157.5:32400/video/:/transcode/universal/start.m3u8?X-Plex-Platform=Chrome©ts=1&mediaIndex=0&offset=0&path=%2Flibrary%2Fmetadata%2F1&X-Plex-Token={0}".format(os.environ.get('PLEX_TEST_TOKEN'))
|
||||
assert a_movie.getStreamURL(videoResolution='800x600') == "http://138.68.157.5:32400/video/:/transcode/universal/start.m3u8?X-Plex-Platform=Chrome©ts=1&mediaIndex=0&offset=0&path=%2Flibrary%2Fmetadata%2F1&videoResolution=800x600&X-Plex-Token={0}".format(os.environ.get('PLEX_TEST_TOKEN'))
|
||||
|
||||
def test_video_Movie_isFullObject_and_reload(pms):
|
||||
movie = pms.library.section('Movies').get('16 Blocks')
|
||||
assert movie.isFullObject() is False
|
||||
movie.reload()
|
||||
assert movie.isFullObject() is True
|
||||
|
||||
movie_via_search = pms.library.search('16 Blocks')[0]
|
||||
assert movie_via_search.isFullObject() is False
|
||||
movie_via_search.reload()
|
||||
assert movie_via_search.isFullObject() is True
|
||||
|
||||
movie_via_section_search = pms.library.section('Movies').search('16 Blocks')[0]
|
||||
assert movie_via_section_search.isFullObject() is False
|
||||
movie_via_section_search.reload()
|
||||
assert movie_via_section_search.isFullObject() is True
|
||||
# If the verify that the object has been reloaded. xml from search only returns 3 actors.
|
||||
assert len(movie_via_section_search.roles) > 3
|
||||
|
||||
|
||||
def test_video_Movie_isPartialObject(a_movie):
|
||||
assert a_movie.isPartialObject()
|
||||
|
||||
|
||||
def test_video_Movie_iterParts(a_movie):
|
||||
assert len(list(a_movie.iterParts())) == 1
|
||||
|
||||
def test_video_Movie_download(monkeydownload, tmpdir, a_movie):
|
||||
downloaded_movie = a_movie.download(savepath=str(tmpdir))
|
||||
assert len(downloaded_movie) == 1
|
||||
|
||||
downloaded_movie2 = a_movie.download(savepath=str(tmpdir), **{'videoResolution': '500x300'})
|
||||
assert len(downloaded_movie2) == 1
|
||||
|
||||
|
||||
|
||||
|
||||
def test_video_Movie_attrs_as_much_as_possible(a_movie_section):
|
||||
m = a_movie_section.get('Cars')
|
||||
|
||||
assert m.location == '/media/movies/cars/cars.mp4'
|
||||
assert str(m.addedAt.date()) == '2017-01-17'
|
||||
assert m.art == '/library/metadata/2/art/1484690715'
|
||||
assert m.audienceRating == 7.9
|
||||
assert m.audienceRatingImage == 'rottentomatoes://image.rating.upright'
|
||||
# Assign 0 m.audioStreams
|
||||
aud0 = m.audioStreams[0]
|
||||
assert m.chapterSource == 'agent'
|
||||
assert m.collections == []
|
||||
assert m.contentRating == 'G'
|
||||
#assert m.countries == [<Country:35:USA>]
|
||||
assert [i.tag for i in m.directors] == ['John Lasseter', 'Joe Ranft']
|
||||
assert m.duration == 170859
|
||||
assert m.fields == []
|
||||
assert [i.tag for i in m.genres] == ['Animation', 'Family', 'Comedy', 'Sport', 'Adventure']
|
||||
assert m.guid == 'com.plexapp.agents.imdb://tt0317219?lang=en'
|
||||
assert m.initpath == '/library/metadata/2'
|
||||
assert m.key == '/library/metadata/2'
|
||||
assert str(m.lastViewedAt) == '2017-01-30 22:19:38' # fix me
|
||||
assert m.librarySectionID == '1'
|
||||
assert m.listType == 'video'
|
||||
# Assign 0 m.media
|
||||
med0 = m.media[0]
|
||||
assert str(m.originalTitle) == '__NA__'
|
||||
assert str(m.originallyAvailableAt.date()) == '2006-06-09'
|
||||
assert m.player is None
|
||||
assert str(m.playlistItemID) == '__NA__'
|
||||
assert str(m.primaryExtraKey) == '__NA__'
|
||||
#assert m.producers == [<Producer:130:Darla.K..Anderson>]
|
||||
assert m.rating == '7.4'
|
||||
assert m.ratingImage == 'rottentomatoes://image.rating.certified'
|
||||
assert m.ratingKey == 2
|
||||
assert [i.tag for i in m.roles] == ['Owen Wilson', 'Paul Newman', 'Bonnie Hunt', 'Larry the Cable Guy', 'Cheech Marin', 'Tony Shalhoub', 'Guido Quaroni', 'Jenifer Lewis', 'Paul Dooley', 'Michael Wallis', 'George Carlin', 'Katherine Helmond', 'John Ratzenberger', 'Michael Keaton', 'Joe Ranft', 'Richard Petty', 'Jeremy Piven', 'Bob Costas', 'Darrell Waltrip', 'Richard Kind', 'Edie McClurg', 'Humpy Wheeler', 'Tom Magliozzi', 'Ray Magliozzi', 'Lynda Petty', 'Andrew Stanton', 'Dale Earnhardt Jr.', 'Michael Schumacher', 'Jay Leno', 'Sarah Clark', 'Mike Nelson', 'Joe Ranft', 'Jonas Rivera', 'Lou Romano', 'Adrian Ochoa', 'E.J. Holowicki', 'Elissa Knight', 'Lindsey Collins', 'Larry Benton', 'Douglas Keever', 'Tom Hanks', 'Tim Allen', 'John Ratzenberger', 'Billy Crystal', 'John Goodman', 'John Ratzenberger', 'Dave Foley', 'John Ratzenberger', 'Vanness Wu']
|
||||
assert m.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert str(m.sessionKey) == '__NA__'
|
||||
assert m.studio == 'Walt Disney Pictures'
|
||||
assert m.summary == u"Lightning McQueen, a hotshot rookie race car driven to succeed, discovers that life is about the journey, not the finish line, when he finds himself unexpectedly detoured in the sleepy Route 66 town of Radiator Springs. On route across the country to the big Piston Cup Championship in California to compete against two seasoned pros, McQueen gets to know the town's offbeat characters."
|
||||
assert m.tagline == "Ahhh... it's got that new movie smell."
|
||||
assert m.thumb == '/library/metadata/2/thumb/1484690715'
|
||||
assert m.title == 'Cars'
|
||||
assert m.titleSort == 'Cars'
|
||||
assert m.transcodeSession is None
|
||||
assert m.type == 'movie'
|
||||
assert str(m.updatedAt.date()) == '2017-01-17'
|
||||
assert str(m.userRating) == '__NA__'
|
||||
assert m.username is None
|
||||
# Assign 0 m.videoStreams
|
||||
vid0 = m.videoStreams[0]
|
||||
assert m.viewCount == 0
|
||||
assert m.viewOffset == 88870
|
||||
assert str(m.viewedAt) == '__NA__'
|
||||
assert [i.tag for i in m.writers] == ['Dan Fogelman', 'Joe Ranft', 'John Lasseter', 'Kiel Murray', 'Phil Lorin', 'Jorgen Klubien']
|
||||
assert m.year == 2006
|
||||
assert aud0.audioChannelLayout == '5.1'
|
||||
assert aud0.bitDepth is None
|
||||
assert aud0.bitrate == 388
|
||||
assert aud0.bitrateMode is None
|
||||
assert aud0.channels == 6
|
||||
assert aud0.codec == 'aac'
|
||||
assert aud0.codecID is None
|
||||
assert aud0.dialogNorm is None
|
||||
assert aud0.duration is None
|
||||
assert aud0.id == 10
|
||||
assert aud0.index == 1
|
||||
assert aud0.initpath == '/library/metadata/2'
|
||||
assert aud0.language is None
|
||||
assert aud0.languageCode is None
|
||||
#assert aud0.part == <MediaPart:2>
|
||||
assert aud0.samplingRate == 48000
|
||||
assert aud0.selected is True
|
||||
assert aud0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert aud0.streamType == 2
|
||||
assert aud0.title is None
|
||||
assert aud0.type == 2
|
||||
assert med0.aspectRatio == 1.78
|
||||
assert med0.audioChannels == 6
|
||||
assert med0.audioCodec == 'aac'
|
||||
assert med0.bitrate == 1474
|
||||
assert med0.container == 'mp4'
|
||||
assert med0.duration == 170859
|
||||
assert med0.height == 720
|
||||
assert med0.id == 2
|
||||
assert med0.initpath == '/library/metadata/2'
|
||||
assert med0.optimizedForStreaming is False
|
||||
# Assign 0 med0.parts
|
||||
par0 = med0.parts[0]
|
||||
assert med0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert med0.video == m
|
||||
assert med0.videoCodec == 'h264'
|
||||
assert med0.videoFrameRate == 'PAL'
|
||||
assert med0.videoResolution == '720'
|
||||
assert med0.width == 1280
|
||||
assert vid0.bitDepth == 8
|
||||
assert vid0.bitrate == 1086
|
||||
assert vid0.cabac is None
|
||||
assert vid0.chromaSubsampling == '4:2:0'
|
||||
assert vid0.codec == 'h264'
|
||||
assert vid0.codecID is None
|
||||
assert vid0.colorSpace is None
|
||||
assert vid0.duration is None
|
||||
assert vid0.frameRate == 25.0
|
||||
assert vid0.frameRateMode is None
|
||||
assert vid0.hasScallingMatrix is None
|
||||
assert vid0.height == 720
|
||||
assert vid0.id == 9
|
||||
assert vid0.index == 0
|
||||
assert vid0.initpath == '/library/metadata/2'
|
||||
assert vid0.language is None
|
||||
assert vid0.languageCode is None
|
||||
assert vid0.level == 31
|
||||
#assert vid0.part == <MediaPart:2>
|
||||
assert vid0.profile == 'main'
|
||||
assert vid0.refFrames == 1
|
||||
assert vid0.scanType is None
|
||||
assert vid0.selected is False
|
||||
assert vid0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert vid0.streamType == 1
|
||||
assert vid0.title is None
|
||||
assert vid0.type == 1
|
||||
assert vid0.width == 1280
|
||||
assert par0.container == 'mp4'
|
||||
assert par0.duration == 170859
|
||||
assert par0.file == '/media/movies/cars/cars.mp4'
|
||||
assert par0.id == 2
|
||||
assert par0.initpath == '/library/metadata/2'
|
||||
assert par0.key == '/library/parts/2/1484679008/file.mp4'
|
||||
#assert par0.media == <Media:Cars>
|
||||
assert par0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert par0.size == 31491130
|
||||
# Assign 0 par0.streams
|
||||
str0 = par0.streams[0]
|
||||
# Assign 1 par0.streams
|
||||
str1 = par0.streams[1]
|
||||
assert str0.bitDepth == 8
|
||||
assert str0.bitrate == 1086
|
||||
assert str0.cabac is None
|
||||
assert str0.chromaSubsampling == '4:2:0'
|
||||
assert str0.codec == 'h264'
|
||||
assert str0.codecID is None
|
||||
assert str0.colorSpace is None
|
||||
assert str0.duration is None
|
||||
assert str0.frameRate == 25.0
|
||||
assert str0.frameRateMode is None
|
||||
assert str0.hasScallingMatrix is None
|
||||
assert str0.height == 720
|
||||
assert str0.id == 9
|
||||
assert str0.index == 0
|
||||
assert str0.initpath == '/library/metadata/2'
|
||||
assert str0.language is None
|
||||
assert str0.languageCode is None
|
||||
assert str0.level == 31
|
||||
#assert str0.part == <MediaPart:2>
|
||||
assert str0.profile == 'main'
|
||||
assert str0.refFrames == 1
|
||||
assert str0.scanType is None
|
||||
assert str0.selected is False
|
||||
assert str0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert str0.streamType == 1
|
||||
assert str0.title is None
|
||||
assert str0.type == 1
|
||||
assert str0.width == 1280
|
||||
assert str1.audioChannelLayout == '5.1'
|
||||
assert str1.bitDepth is None
|
||||
assert str1.bitrate == 388
|
||||
assert str1.bitrateMode is None
|
||||
assert str1.channels == 6
|
||||
assert str1.codec == 'aac'
|
||||
assert str1.codecID is None
|
||||
assert str1.dialogNorm is None
|
||||
assert str1.duration is None
|
||||
assert str1.id == 10
|
||||
assert str1.index == 1
|
||||
assert str1.initpath == '/library/metadata/2'
|
||||
assert str1.language is None
|
||||
assert str1.languageCode is None
|
||||
#assert str1.part == <MediaPart:2>
|
||||
assert str1.samplingRate == 48000
|
||||
assert str1.selected is True
|
||||
assert str1.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert str1.streamType == 2
|
||||
assert str1.title is None
|
||||
assert str1.type == 2
|
||||
|
||||
|
||||
|
||||
def test_video_Show(a_show):
|
||||
assert a_show.title == 'The 100'
|
||||
|
||||
|
||||
def test_video_Show_attrs(a_show):
|
||||
m = a_show
|
||||
assert str(m.addedAt.date()) == '2017-01-17'
|
||||
assert '/library/metadata/12/art/' in m.art
|
||||
assert '/library/metadata/12/banner/' in m.banner
|
||||
assert m.childCount == 2
|
||||
assert m.contentRating == 'TV-14'
|
||||
assert m.duration == 2700000
|
||||
assert m.initpath == '/library/sections/2/all'
|
||||
# Since we access m.genres the show is forced to reload
|
||||
assert [i.tag for i in m.genres] == ['Drama', 'Science-Fiction', 'Suspense', 'Thriller']
|
||||
# So the initkey should have changed because of the reload
|
||||
assert m.initpath == '/library/metadata/12'
|
||||
assert m.index == '1'
|
||||
assert m.key == '/library/metadata/12'
|
||||
assert str(m.lastViewedAt.date()) == '2017-01-22'
|
||||
assert m.leafCount == 9
|
||||
assert m.listType == 'video'
|
||||
assert m.location == '/media/tvshows/the 100'
|
||||
assert str(m.originallyAvailableAt.date()) == '2014-03-19'
|
||||
assert m.rating == 8.1
|
||||
assert m.ratingKey == 12
|
||||
assert [i.tag for i in m.roles][:3] == ['Richard Harmon', 'Alycia Debnam-Carey', 'Lindsey Morgan']
|
||||
assert [i.tag for i in m.actors][:3] == ['Richard Harmon', 'Alycia Debnam-Carey', 'Lindsey Morgan']
|
||||
assert m.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert m.studio == 'The CW'
|
||||
assert m.summary == u"When nuclear Armageddon destroys civilization on Earth, the only survivors are those on the 12 international space stations in orbit at the time. Three generations later, the 4,000 survivors living on a space ark of linked stations see their resources dwindle and face draconian measures established to ensure humanity's future. Desperately looking for a solution, the ark's leaders send 100 juvenile prisoners back to the planet to test its habitability. Having always lived in space, the exiles find the planet fascinating and terrifying, but with the fate of the human race in their hands, they must forge a path into the unknown."
|
||||
assert '/library/metadata/12/theme/' in m.theme
|
||||
assert '/library/metadata/12/thumb/' in m.thumb
|
||||
assert m.title == 'The 100'
|
||||
assert m.titleSort == '100'
|
||||
assert m.type == 'show'
|
||||
assert str(m.updatedAt.date()) == '2017-01-22'
|
||||
assert m.viewCount == 1
|
||||
assert m.viewedLeafCount == 1
|
||||
assert m.year == 2014
|
||||
|
||||
|
||||
def test_video_Show_watched(a_show):
|
||||
watched = a_show.watched()
|
||||
assert len(watched) == 1 and watched[0].title == 'Pilot'
|
||||
|
||||
def test_video_Show_unwatched(a_show):
|
||||
assert len(a_show.unwatched()) == 8
|
||||
|
||||
def test_video_Show_location(pms):
|
||||
# This should be a part of test test_video_Show_attrs
|
||||
# But is excluded because of https://github.com/mjs7231/python-plexapi/issues/97
|
||||
s = pms.library.section('TV Shows').get('The 100')
|
||||
# This will require a reload since the xml from http://138.68.157.5:32400/library/sections/2/all
|
||||
# Does not contain a location
|
||||
assert s.location == '/media/tvshows/the 100'
|
||||
|
||||
def test_video_Show_reload(pms):
|
||||
s = pms.library.section('TV Shows').get('Game of Thrones')
|
||||
assert s.initpath == '/library/sections/2/all'
|
||||
s.reload()
|
||||
assert s.initpath == '/library/metadata/6'
|
||||
assert len(s.roles) > 3
|
||||
|
||||
|
||||
|
||||
def test_video_Show_episodes(a_show):
|
||||
inc_watched = a_show.episodes()
|
||||
ex_watched = a_show.episodes(watched=False)
|
||||
assert len(inc_watched) == 9
|
||||
assert len(ex_watched) == 8
|
||||
|
||||
def test_video_Show_download(monkeydownload, tmpdir, a_show):
|
||||
f = a_show.download(savepath=str(tmpdir))
|
||||
assert len(f) == 9
|
||||
|
||||
|
||||
def test_video_Season_download(monkeydownload, tmpdir, a_show):
|
||||
sn = a_show.season('Season 1')
|
||||
|
||||
f = sn.download(savepath=str(tmpdir))
|
||||
assert len(f) == 8
|
||||
|
||||
def test_video_Episode_download(monkeydownload, tmpdir, a_episode):
|
||||
f = a_episode.download(savepath=str(tmpdir))
|
||||
assert len(f) == 1
|
||||
|
||||
with_sceen_size = a_episode.download(savepath=str(tmpdir), **{'videoResolution': '500x300'})
|
||||
assert len(with_sceen_size) == 1
|
||||
|
||||
|
||||
|
||||
|
||||
def test_video_Show_thumbUrl(a_show):
|
||||
assert 'http://138.68.157.5:32400/library/metadata/12/thumb/' in a_show.thumbUrl
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_video_Show_analyze(a_show):
|
||||
show = a_show.analyze() # this isnt possble.. should it even be available?
|
||||
|
||||
def test_video_Show_markWatched(a_tv_section):
|
||||
show = a_tv_section.get("Marvel's Daredevil")
|
||||
show.markWatched()
|
||||
assert a_tv_section.get("Marvel's Daredevil").isWatched
|
||||
|
||||
|
||||
def test_video_Show_markUnwatched(a_tv_section):
|
||||
show = a_tv_section.get("Marvel's Daredevil")
|
||||
show.markUnwatched()
|
||||
assert not a_tv_section.get("Marvel's Daredevil").isWatched
|
||||
|
||||
|
||||
def test_video_Show_refresh(a_tv_section):
|
||||
show = a_tv_section.get("Marvel's Daredevil")
|
||||
show.refresh()
|
||||
|
||||
|
||||
def test_video_Show_get(a_show):
|
||||
assert a_show.get('Pilot').title == 'Pilot'
|
||||
|
||||
|
||||
def test_video_Show_isWatched(a_show):
|
||||
assert not a_show.isWatched
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_video_Show_section(a_show): # BROKEN!
|
||||
show = a_show.section()
|
||||
|
||||
|
||||
def test_video_Episode(a_show):
|
||||
pilot = a_show.episode('Pilot')
|
||||
assert pilot == a_show.episode(season=1, episode=1)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
a_show.episode()
|
||||
|
||||
with pytest.raises(NotFound):
|
||||
a_show.episode(season=1337, episode=1337)
|
||||
|
||||
def test_video_Episode_analyze(a_tv_section):
|
||||
ep = a_tv_section.get("Marvel's Daredevil").episode(season=1, episode=1)
|
||||
ep.analyze()
|
||||
|
||||
|
||||
def test_video_Episode_attrs(a_episode):
|
||||
ep = a_episode
|
||||
assert str(ep.addedAt.date()) == '2017-01-17'
|
||||
assert ep.contentRating == 'TV-14'
|
||||
assert [i.tag for i in ep.directors] == ['Bharat Nalluri']
|
||||
assert ep.duration == 170859
|
||||
assert ep.grandparentTitle == 'The 100'
|
||||
assert ep.index == 1
|
||||
assert ep.initpath == '/library/metadata/12/allLeaves'
|
||||
assert ep.key == '/library/metadata/14'
|
||||
assert ep.listType == 'video'
|
||||
# Assign 0 ep.media
|
||||
med0 = ep.media[0]
|
||||
assert str(ep.originallyAvailableAt.date()) == '2014-03-19'
|
||||
assert ep.parentIndex == '1'
|
||||
assert ep.parentKey == '/library/metadata/13'
|
||||
assert ep.parentRatingKey == 13
|
||||
assert '/library/metadata/13/thumb/' in ep.parentThumb
|
||||
#assert ep.parentThumb == '/library/metadata/13/thumb/1485096623'
|
||||
assert ep.player is None
|
||||
assert ep.rating == 7.4
|
||||
assert ep.ratingKey == 14
|
||||
assert ep.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert ep.summary == u'Ninety-seven years ago, nuclear Armageddon decimated planet Earth, destroying civilization. The only survivors were the 400 inhabitants of 12 international space stations that were in orbit at the time. Three generations have been born in space, the survivors now number 4,000, and resources are running out on their dying "Ark." Among the 100 young exiles are Clarke, the bright teenage daughter of the Ark’s chief medical officer; the daredevil Finn; the brother/sister duo of Bellamy and Octavia, whose illegal sibling status has always led them to flaunt the rules, the lighthearted Jasper and the resourceful Monty. Technologically blind to what’s happening on the planet below them, the Ark’s leaders — Clarke’s widowed mother, Abby; Chancellor Jaha; and his shadowy second in command, Kane — are faced with difficult decisions about life, death and the continued existence of the human race.'
|
||||
assert ep.thumb == '/library/metadata/14/thumb/1485115318'
|
||||
assert ep.title == 'Pilot'
|
||||
assert ep.titleSort == 'Pilot'
|
||||
assert ep.transcodeSession is None
|
||||
assert ep.type == 'episode'
|
||||
assert str(ep.updatedAt.date()) == '2017-01-22'
|
||||
assert ep.username is None
|
||||
assert ep.viewCount == 1
|
||||
assert ep.viewOffset == 0
|
||||
assert [i.tag for i in ep.writers] == ['Jason Rothenberg']
|
||||
assert ep.year == 2014
|
||||
assert med0.aspectRatio == 1.78
|
||||
assert med0.audioChannels == 6
|
||||
assert med0.audioCodec == 'aac'
|
||||
assert med0.bitrate == 1474
|
||||
assert med0.container == 'mp4'
|
||||
assert med0.duration == 170859
|
||||
assert med0.height == 720
|
||||
assert med0.id == 12
|
||||
assert med0.initpath == '/library/metadata/12/allLeaves'
|
||||
assert med0.optimizedForStreaming is False
|
||||
# Assign 0 med0.parts
|
||||
par0 = med0.parts[0]
|
||||
assert med0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
#assert med0.video == <Episode:14:The 100:S1:E1:Pilot>
|
||||
assert med0.videoCodec == 'h264'
|
||||
assert med0.videoFrameRate == 'PAL'
|
||||
assert med0.videoResolution == '720'
|
||||
assert med0.width == 1280
|
||||
assert par0.container == 'mp4'
|
||||
assert par0.duration == 170859
|
||||
assert par0.file == '/media/tvshows/the 100/season 1/the.100.s01e01.mp4'
|
||||
assert par0.id == 12
|
||||
assert par0.initpath == '/library/metadata/12/allLeaves'
|
||||
assert par0.key == '/library/parts/12/1484679008/file.mp4'
|
||||
#assert par0.media == <Media:Pilot>
|
||||
assert par0.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert par0.size == 31491130
|
||||
assert ep.isWatched is True
|
||||
|
||||
|
||||
def test_video_Season(a_show):
|
||||
seasons = a_show.seasons()
|
||||
assert len(seasons) == 2
|
||||
assert ['Season 1', 'Season 2'] == [s.title for s in seasons]
|
||||
assert a_show.season('Season 1') == seasons[0]
|
||||
|
||||
|
||||
def test_video_Season_attrs(a_show):
|
||||
m = a_show.season('Season 1')
|
||||
assert str(m.addedAt.date()) == '2017-01-17'
|
||||
assert m.index == 1
|
||||
assert m.initpath == '/library/metadata/12/children'
|
||||
assert m.key == '/library/metadata/13'
|
||||
assert str(m.lastViewedAt.date()) == '2017-01-22'
|
||||
assert m.leafCount == 8
|
||||
assert m.listType == 'video'
|
||||
assert m.parentKey == '/library/metadata/12'
|
||||
assert m.parentRatingKey == 12
|
||||
assert m.parentTitle == 'The 100'
|
||||
assert m.ratingKey == 13
|
||||
assert m.server.baseurl == 'http://138.68.157.5:32400'
|
||||
assert m.summary == ''
|
||||
assert '/library/metadata/13/thumb/' in m.thumb
|
||||
#assert m.thumb == '/library/metadata/13/thumb/1485096623'
|
||||
assert m.title == 'Season 1'
|
||||
assert m.titleSort == 'Season 1'
|
||||
assert m.type == 'season'
|
||||
assert str(m.updatedAt.date()) == '2017-01-22'
|
||||
assert m.viewCount == 1
|
||||
assert m.viewedLeafCount == 1
|
||||
assert m.seasonNumber == 1
|
||||
|
||||
|
||||
def test_video_Season_show(a_show):
|
||||
sn = a_show.seasons()[0]
|
||||
season_by_name = a_show.season('Season 1')
|
||||
assert a_show.ratingKey == sn.parentRatingKey and season_by_name.parentRatingKey
|
||||
assert sn.ratingKey == season_by_name.ratingKey
|
||||
|
||||
|
||||
def test_video_Season_watched(a_tv_section):
|
||||
show = a_tv_section.get("Marvel's Daredevil")
|
||||
sn = show.season(1)
|
||||
sne = show.season('Season 1')
|
||||
assert sn == sne
|
||||
sn.markWatched()
|
||||
assert sn.isWatched
|
||||
|
||||
|
||||
def test_video_Season_unwatched(a_tv_section):
|
||||
sn = a_tv_section.get("Marvel's Daredevil").season(1)
|
||||
sn.markUnwatched()
|
||||
assert not sn.isWatched
|
||||
|
||||
|
||||
def test_video_Season_get(a_show):
|
||||
ep = a_show.season(1).get('Pilot')
|
||||
assert ep.title == 'Pilot'
|
||||
|
||||
|
||||
def test_video_Season_episode(a_show):
|
||||
ep = a_show.season(1).get('Pilot')
|
||||
assert ep.title == 'Pilot'
|
||||
|
||||
|
||||
def test_video_Season_episodes(a_show):
|
||||
sn_eps = a_show.season(2).episodes()
|
||||
assert len(sn_eps) == 1
|
||||
|
||||
|
||||
|
||||
#### MISC
|
||||
def test_that_reload_return_the_same_object(pms):
|
||||
# we want to check this that all the urls are correct
|
||||
movie_library_search = pms.library.section('Movies').search('16 Blocks')[0]
|
||||
movie_search = pms.search('16 Blocks')[0]
|
||||
movie_section_get = pms.library.section('Movies').get('16 Blocks')
|
||||
|
||||
movie_library_search_key = movie_library_search.key
|
||||
movie_search_key = movie_search.key
|
||||
movie_section_get_key = movie_section_get.key
|
||||
|
||||
assert movie_library_search_key == movie_library_search.reload().key == movie_search_key == movie_search.reload().key == movie_section_get_key == movie_section_get.reload().key
|
||||
|
||||
tvshow_library_search = pms.library.section('TV Shows').search('The 100')[0]
|
||||
tvshow_search = pms.search('The 100')[0]
|
||||
tvshow_section_get = pms.library.section('TV Shows').get('The 100')
|
||||
|
||||
tvshow_library_search_key = tvshow_library_search.key
|
||||
tvshow_search_key = tvshow_search.key
|
||||
tvshow_section_get_key = tvshow_section_get.key
|
||||
|
||||
assert tvshow_library_search_key == tvshow_library_search.reload().key == tvshow_search_key == tvshow_search.reload().key == tvshow_section_get_key == tvshow_section_get.reload().key
|
||||
|
||||
season_library_search = tvshow_library_search.season(1)
|
||||
season_search = tvshow_search.season(1)
|
||||
season_section_get = tvshow_section_get.season(1)
|
||||
|
||||
season_library_search_key = season_library_search.key
|
||||
season_search_key = season_search.key
|
||||
season_section_get_key = season_section_get.key
|
||||
|
||||
assert season_library_search_key == season_library_search.reload().key == season_search_key == season_search.reload().key == season_section_get_key == season_section_get.reload().key
|
||||
|
||||
episode_library_search = tvshow_library_search.episode(season=1, episode=1)
|
||||
episode_search = tvshow_search.episode(season=1, episode=1)
|
||||
episode_section_get = tvshow_section_get.episode(season=1, episode=1)
|
||||
|
||||
episode_library_search_key = episode_library_search.key
|
||||
episode_search_key = episode_search.key
|
||||
episode_section_get_key = episode_section_get.key
|
||||
|
||||
assert episode_library_search_key == episode_library_search.reload().key == episode_search_key == episode_search.reload().key == episode_section_get_key == episode_section_get.reload().key
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in a new issue