testing testing, 1, 2, 3.

This commit is contained in:
Hellowlol 2017-01-09 15:21:54 +01:00
parent 7367e531a2
commit 7e9bd51d55
20 changed files with 1896 additions and 54 deletions

View file

@ -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
'''

View file

@ -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

View file

@ -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.

View file

@ -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):

View file

@ -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({

View file

@ -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

View file

@ -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))

View file

@ -53,7 +53,7 @@ class Video(PlexPartialObject):
that are useful to knowwhether 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
View file

@ -0,0 +1,6 @@
requests
pytest
pytest-cov
betamax
betamax_serializers
pillow

View 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()

View 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()

View 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

View file

@ -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)

View 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'

View 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.'

View 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.'

View file

@ -0,0 +1,3 @@
# test search.
# Many more tests is for search later.

View 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'

View 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

View 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&copyts=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&copyts=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 Arks 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 whats happening on the planet below them, the Arks leaders — Clarkes 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