mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-25 21:20:18 +00:00
testing testing, 1, 2, 3.
This commit is contained in:
parent
7367e531a2
commit
7e9bd51d55
20 changed files with 1896 additions and 54 deletions
|
@ -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,15 @@ 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 +302,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
|
||||
|
|
|
@ -71,7 +71,6 @@ class Library(object):
|
|||
Parameters:
|
||||
sectionID (int): ID of the section to return.
|
||||
"""
|
||||
if not self._sectionsByID:
|
||||
self.sections()
|
||||
return self._sectionsByID[sectionID]
|
||||
|
||||
|
@ -89,13 +88,15 @@ class Library(object):
|
|||
""" Returns a list of all media items recently added. """
|
||||
return utils.listItems(self.server, '/library/recentlyAdded')
|
||||
|
||||
def get(self, 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() == tite.lower():
|
||||
reutrn i
|
||||
|
||||
def getByKey(self, key):
|
||||
""" Return the first item from all items with the specified key.
|
||||
|
|
|
@ -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)
|
||||
print(response.url)
|
||||
if response.status_code not in [200, 201]:
|
||||
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
|
||||
|
||||
|
|
110
plexapi/utils.py
110
plexapi/utils.py
|
@ -1,10 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging, re
|
||||
import os
|
||||
from datetime import datetime
|
||||
from plexapi.compat import quote, urlencode, string_type
|
||||
import requests
|
||||
|
||||
from plexapi.exceptions import NotFound, UnknownType, Unsupported
|
||||
from threading import Thread
|
||||
|
||||
|
||||
# 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}
|
||||
|
@ -73,6 +77,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 +92,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)
|
||||
|
||||
|
@ -114,6 +120,7 @@ class PlexPartialObject(object):
|
|||
data = self.server.query(self.key)
|
||||
self.initpath = self.key
|
||||
self._loadData(data[0])
|
||||
self._reloaded = True
|
||||
|
||||
|
||||
class Playable(object):
|
||||
|
@ -170,7 +177,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 +195,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.
|
||||
|
@ -466,6 +506,7 @@ def threaded(callback, listargs):
|
|||
threads[-1].start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
@ -482,3 +523,70 @@ 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()
|
||||
|
||||
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):
|
||||
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:
|
||||
log.exception('Failed to download %s to %s %s' % (url, fullpath, e))
|
||||
|
|
130
plexapi/video.py
130
plexapi/video.py
|
@ -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,51 @@ 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))
|
||||
'''
|
||||
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
|
||||
'''
|
||||
|
|
6
requirements_dev.txt
Normal file
6
requirements_dev.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
requests
|
||||
pytest
|
||||
pytest-cov
|
||||
betamax
|
||||
betamax_serializers
|
||||
pillow
|
143
tests/tests_pytest/conftest.py
Normal file
143
tests/tests_pytest/conftest.py
Normal file
|
@ -0,0 +1,143 @@
|
|||
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
|
||||
|
||||
|
||||
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_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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,37 +1,89 @@
|
|||
import os
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
|
||||
from plexapi.server import PlexServer
|
||||
|
||||
|
||||
for k, v in os.environ.items():
|
||||
print k, v
|
||||
|
||||
print os.environ.get('kek')
|
||||
@pytest.fixture
|
||||
def session():
|
||||
return requests.Session()
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def pms():
|
||||
#username = os.environ.get('plex_username')
|
||||
#password = os.environ.get('plex_password')
|
||||
token = os.environ.get('PLEX_TOKEN')
|
||||
url = 'http://10.0.0.97:32400'
|
||||
|
||||
assert token
|
||||
assert url
|
||||
|
||||
pms = PlexServer(url, token, session=requests.Session())
|
||||
return pms
|
||||
|
||||
#@pms
|
||||
def test_library(pms):
|
||||
def test_section(pms):
|
||||
sections = pms.library.sections()
|
||||
print len(sections)
|
||||
assert len(sections) == 4
|
||||
|
||||
lfs = 'TV Shows'
|
||||
section_name = pms.library.section(lfs)
|
||||
assert section_name.title == lfs
|
||||
|
||||
|
||||
def test_library_sectionByID_is_equal_section(pms):
|
||||
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'
|
||||
|
||||
|
||||
#### 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 movies.search(actor=movie.actors[0]), '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)
|
||||
|
|
77
tests/tests_pytest/test_myplex.py
Normal file
77
tests/tests_pytest/test_myplex.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
# -*- 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 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.'
|
115
tests/tests_pytest/test_playlist.py
Normal file
115
tests/tests_pytest/test_playlist.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
def test_list_playlists(pms):
|
||||
playlists = pms.playlists()
|
||||
print(playlists)
|
||||
assert len(playlists)
|
||||
|
||||
|
||||
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.
|
183
tests/tests_pytest/test_server.py
Normal file
183
tests/tests_pytest/test_server.py
Normal file
|
@ -0,0 +1,183 @@
|
|||
import pytest
|
||||
import os
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_server_playlist(pms):
|
||||
pl = pms.playlist('some_playlist')
|
||||
assert pl.title == 'some_playlist'
|
||||
|
||||
|
||||
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_token_in_headers(pms):
|
||||
h = pms.headers()
|
||||
assert 'X-Plex-Token' in h and len(h['X-Plex-Token'])
|
||||
'''
|
||||
{
|
||||
'X-Plex-Platform': plexapi.X_PLEX_PLATFORM,
|
||||
'X-Plex-Platform-Version': plexapi.X_PLEX_PLATFORM_VERSION,
|
||||
'X-Plex-Provides': plexapi.X_PLEX_PROVIDES,
|
||||
'X-Plex-Product': plexapi.X_PLEX_PRODUCT,
|
||||
'X-Plex-Version': plexapi.X_PLEX_VERSION,
|
||||
'X-Plex-Device': plexapi.X_PLEX_DEVICE,
|
||||
'X-Plex-Device-Name': plexapi.X_PLEX_DEVICE_NAME,
|
||||
'X-Plex-Client-Identifier': plexapi.X_PLEX_IDENTIFIER,
|
||||
}
|
||||
'''
|
||||
|
||||
def _test_server_createPlayQueue():
|
||||
# see test_playlists.py
|
||||
pass
|
||||
|
||||
def _test_server_createPlaylist():
|
||||
# see test_playlists.py
|
||||
pass
|
||||
|
||||
@pytest.mark.req_client
|
||||
def test_server_client(pms):
|
||||
assert pms.client('Plex Web (Chrome)')
|
||||
|
||||
|
||||
@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'
|
||||
|
||||
|
||||
|
92
tests/tests_pytest/test_utils.py
Normal file
92
tests/tests_pytest/test_utils.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
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
|
||||
|
||||
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():
|
||||
pass
|
||||
|
||||
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
|
491
tests/tests_pytest/test_video.py
Normal file
491
tests/tests_pytest/test_video.py
Normal file
|
@ -0,0 +1,491 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#test_the_file_class_method
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
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_attrs_as_much_as_possible(a_movie_section):
|
||||
m = a_movie_section.get('Cars')
|
||||
|
||||
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) == '__NA__'
|
||||
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 == 0
|
||||
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 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_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(tmpdir, a_show):
|
||||
f = a_show.download(savepath=str(tmpdir))
|
||||
assert len(f) == 9
|
||||
|
||||
|
||||
def _test_video_Season_download(tmpdir, a_show):
|
||||
sn = a_show.season('Season 1')
|
||||
|
||||
f = sn.download(savepath=str(tmpdir))
|
||||
assert len(f) == 8
|
||||
|
||||
def test_video_Episode_download(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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
Loading…
Reference in a new issue