Cleanup formatting

This commit is contained in:
Michael Shepanski 2017-02-01 22:53:05 -05:00
parent 6e761f0fc5
commit cec0ab07e8
13 changed files with 137 additions and 175 deletions

1
.gitignore vendored
View file

@ -17,7 +17,6 @@ include/
lib/
pip-selfcheck.json
pyvenv.cfg
htmlcov
.coverage
*.orig

View file

@ -1,2 +1,2 @@
include README.md
include requirements.pip
include requirements.txt

View file

@ -132,13 +132,24 @@ class Artist(Audio):
return self.track(title)
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
""" Downloads all tracks for this artist to the specified location.
Parameters:
savepath (str): Title of the track to return.
keep_orginal_name (bool): Set True to keep the original filename as stored in
the Plex server. False will create a new filename with the format
"<Atrist> - <Album> <Track>".
kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will
be returned and the additional arguments passed in will be sent to that
function. If kwargs is not specified, the media items will be downloaded
and saved to disk.
"""
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
@ -203,6 +214,18 @@ class Album(Audio):
return utils.listItems(self.server, self.parentKey)[0]
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
""" Downloads all tracks for this artist to the specified location.
Parameters:
savepath (str): Title of the track to return.
keep_orginal_name (bool): Set True to keep the original filename as stored in
the Plex server. False will create a new filename with the format
"<Atrist> - <Album> <Track>".
kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will
be returned and the additional arguments passed in will be sent to that
function. If kwargs is not specified, the media items will be downloaded
and saved to disk.
"""
downloaded = []
for ep in self.tracks():
dl = ep.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
@ -276,7 +299,7 @@ class Track(Audio, Playable):
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]
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)
@ -302,37 +325,5 @@ class Track(Audio, Playable):
return utils.listItems(self.server, self.grandparentKey)[0]
def _prettyfilename(self):
""" Returns a filename for use in download. """
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

@ -47,10 +47,8 @@ class PlexClient(object):
self.token = token
self.server = server
# session > server.session > requests.Session
if server:
self.session = session or server.session
else:
self.session = session or requests.Session()
_server_session = server.session if server else None
self.session = session or _server_session or requests.Session()
self._loadData(data) if data is not None else self.connect()
self._proxyThroughServer = False
self._commandId = 0
@ -405,8 +403,7 @@ class PlexClient(object):
:class:`~plexapi.exceptions.Unsupported`: When no PlexServer specified in this object.
"""
if not self.server:
raise Unsupported(
'A server must be specified before using this command.')
raise Unsupported('A server must be specified before using this command.')
server_url = media.server.baseurl.split(':')
playqueue = self.server.createPlayQueue(media)
self.sendCommand('playback/playMedia', **dict({

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
# Python 2/3 compatability
# Always try Py3 first
import sys
try:
string_type = basestring

View file

@ -25,4 +25,5 @@ class Unauthorized(PlexApiException):
pass
class NotImplementedError(PlexApiException):
""" Feature is not yet implemented. """
pass

View file

@ -23,7 +23,7 @@ class Library(object):
self.server = server
self.title1 = data.attrib.get('title1')
self.title2 = data.attrib.get('title2')
self._sectionsByID = {} # cached section UUIDs
self._sectionsByID = {} # cached Section UUIDs
def __repr__(self):
return '<Library:%s>' % self.title1.encode('utf8')
@ -131,9 +131,8 @@ class Library(object):
removed. Removing these old bundles can reduce the size of your install. By default, your
server will automatically clean up old bundles once a week as part of Scheduled Tasks.
"""
# TODO: Should this check the response for success or the correct mediaprefix?
self.server.query('/library/clean/bundles')
# Should this return true or false?
# check element if if has the correct mediaprefix?
def emptyTrash(self):
""" If a library has items in the Library Trash, use this option to empty the Trash. """
@ -380,9 +379,9 @@ class MovieSection(LibrarySection):
TYPE (str): 'movie'
"""
ALLOWED_FILTERS = ('unwatched', 'duplicate', 'year', 'decade', 'genre', 'contentRating',
'collection', 'director', 'actor', 'country', 'studio', 'resolution')
'collection', 'director', 'actor', 'country', 'studio', 'resolution')
ALLOWED_SORT = ('addedAt', 'originallyAvailableAt', 'lastViewedAt', 'titleSort', 'rating',
'mediaHeight', 'duration')
'mediaHeight', 'duration')
TYPE = 'movie'
@ -398,7 +397,7 @@ class ShowSection(LibrarySection):
"""
ALLOWED_FILTERS = ('unwatched', 'year', 'genre', 'contentRating', 'network', 'collection')
ALLOWED_SORT = ('addedAt', 'lastViewedAt', 'originallyAvailableAt', 'titleSort',
'rating', 'unwatched')
'rating', 'unwatched')
TYPE = 'show'
def searchShows(self, **kwargs):

View file

@ -174,15 +174,49 @@ class MediaTag(object):
return '<%s:%s:%s>' % (self.__class__.__name__, self.id, tag)
class Collection(MediaTag): TYPE = 'Collection'; FILTER = 'collection'
class Country(MediaTag): TYPE = 'Country'; FILTER = 'country'
class Director(MediaTag): TYPE = 'Director'; FILTER = 'director'
class Genre(MediaTag): TYPE = 'Genre'; FILTER = 'genre'
class Mood(MediaTag): TYPE = 'Mood'; FILTER = 'mood'
class Producer(MediaTag): TYPE = 'Producer'; FILTER = 'producer'
class Role(MediaTag): TYPE = 'Role'; FILTER = 'role'
class Similar(MediaTag): TYPE = 'Similar'; FILTER = 'similar'
class Writer(MediaTag): TYPE = 'Writer'; FILTER = 'writer'
class Collection(MediaTag):
TYPE = 'Collection'
FILTER = 'collection'
class Country(MediaTag):
TYPE = 'Country'
FILTER = 'country'
class Director(MediaTag):
TYPE = 'Director'
FILTER = 'director'
class Genre(MediaTag):
TYPE = 'Genre'
FILTER = 'genre'
class Mood(MediaTag):
TYPE = 'Mood'
FILTER = 'mood'
class Producer(MediaTag):
TYPE = 'Producer'
FILTER = 'producer'
class Role(MediaTag):
TYPE = 'Role'
FILTER = 'role'
class Similar(MediaTag):
TYPE = 'Similar'
FILTER = 'similar'
class Writer(MediaTag):
TYPE = 'Writer'
FILTER = 'writer'
class Field(object):

View file

@ -72,7 +72,7 @@ class MyPlexAccount(object):
self.title = data.attrib.get('title')
self.username = data.attrib.get('username')
self.uuid = data.attrib.get('uuid')
# TODO: Complete these items!
# TODO: Fetch missing MyPlexAccount attributes
self.subscriptionActive = None # renamed on server
self.subscriptionStatus = None # renamed on server
self.subscriptionPlan = None # renmaed on server
@ -143,8 +143,7 @@ class MyPlexAccount(object):
if response.status_code != requests.codes.created:
codename = codes.get(response.status_code)[0]
if response.status_code == 401:
raise Unauthorized('(%s) %s' %
(response.status_code, codename))
raise Unauthorized('(%s) %s' % (response.status_code, codename))
raise BadRequest('(%s) %s' % (response.status_code, codename))
data = ElementTree.fromstring(response.text.encode('utf8'))
return MyPlexAccount(data, cls.SIGNIN, session=sess)
@ -348,7 +347,6 @@ class MyPlexDevice(object):
vendor (str): Device vendor (ubuntu, etc).
version (str): Unknown (1, 2, 1.3.3.3148-b38628e, 1.3.15, etc.)
"""
BASEURL = 'https://plex.tv/devices.xml'
def __init__(self, data):

View file

@ -34,10 +34,8 @@ class PlayQueue(object):
self.mediaTagPrefix = data.attrib.get('mediaTagPrefix')
self.mediaTagVersion = data.attrib.get('mediaTagVersion')
self.playQueueID = data.attrib.get('playQueueID')
self.playQueueSelectedItemID = data.attrib.get(
'playQueueSelectedItemID')
self.playQueueSelectedItemOffset = data.attrib.get(
'playQueueSelectedItemOffset')
self.playQueueSelectedItemID = data.attrib.get('playQueueSelectedItemID')
self.playQueueSelectedItemOffset = data.attrib.get('playQueueSelectedItemOffset')
self.playQueueTotalCount = data.attrib.get('playQueueTotalCount')
self.playQueueVersion = data.attrib.get('playQueueVersion')
self.items = [utils.buildItem(server, elem, initpath) for elem in data]

View file

@ -1,24 +1,13 @@
# -*- coding: utf-8 -*-
import logging
import os
import re
import logging, os, re, requests
from datetime import datetime
from threading import Thread
import requests
from plexapi.compat import quote, string_type, urlencode
from plexapi.exceptions import NotFound, NotImplementedError, UnknownType, Unsupported
#from plexapi import log
# Search Types - Plex uses these to filter specific media types when searching.
SEARCHTYPES = {'movie': 1, 'show': 2, 'season': 3,
'episode': 4, 'artist': 8, 'album': 9, 'track': 10,
'photo': 14}
SEARCHTYPES = {'movie': 1, 'show': 2, 'season': 3, 'episode': 4,
'artist': 8, 'album': 9, 'track': 10, 'photo': 14}
LIBRARY_TYPES = {}
@ -50,9 +39,7 @@ class _NA(object):
def __repr__(self):
return '__NA__'
# Lets do this for now.
NA = _NA()
NA = _NA() # Keep this for now.
class SecretsFilter(logging.Filter):
@ -208,35 +195,35 @@ 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.
""" Downloads this items media to the specified location. Returns a list of
filepaths that have been saved to disk.
Parameters:
savepath (str): Title of the track to return.
keep_orginal_name (bool): Set True to keep the original filename as stored in
the Plex server. False will create a new filename with the format
"<Atrist> - <Album> <Track>".
kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will
be returned and the additional arguments passed in will be sent to that
function. If kwargs is not specified, the media items will be downloaded
and saved to disk.
"""
downloaded = []
locs = [i for i in self.iterParts() if i]
for loc in locs:
filepaths = []
locations = [i for i in self.iterParts() if i]
for location in locations:
filename = location.file
if keep_orginal_name is False:
name = '%s.%s' % (self._prettyfilename(), loc.container)
else:
name = loc.file
filename = '%s.%s' % (self._prettyfilename(), location.container)
# 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
download_url = self.server.url('%s?download=1' % location.key)
filepath = download(download_url, filename=filename, savepath=savepath,
session=self.server.session)
if filepath:
filepaths.append(filepath)
return filepaths
def buildItem(server, elem, initpath, bytag=False):
@ -538,27 +525,22 @@ def toDatetime(value, format=None):
def download(url, filename=None, savepath=None, session=None, chunksize=4024, mocked=False):
"""Helper to download a thumb, videofile or something.
""" Helper to download a thumb, videofile or other media item. Returns the local
path to the downloaded file.
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
Parameters:
url (str): URL where the content be reached.
filename (str): Filename of the downloaded file, default None.
savepath (str): Defaults to current working dir.
chunksize (int): What chunksize read/write at the time.
mocked (bool): Helper to do evertything except write the file.
Example:
>>> download(a_episode.getStreamURL(), a_episode.location)
/path/to/file
Returns:
/path/to/file or None
"""
session = session or requests.Session()
print('Mocked download %s' % mocked)
if savepath is None:
savepath = os.getcwd()
else:
@ -568,13 +550,10 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, mo
except OSError:
if not os.path.isdir(savepath): # pragma: no cover
raise
filename = os.path.basename(filename)
fullpath = os.path.join(savepath, filename)
try:
response = session.get(url, stream=True)
# images dont have a extention so we try
# to guess it from content-type
ext = os.path.splitext(fullpath)[-1]
@ -582,25 +561,18 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, mo
ext = ''
else:
cp = response.headers.get('content-type')
if cp:
if 'image' in cp:
ext = '.%s' % cp.split('/')[1]
fullpath = '%s%s' % (fullpath, ext)
if mocked:
return fullpath
with open(fullpath, 'wb') as f:
for chunk in response.iter_content(chunk_size=chunksize):
if chunk:
f.write(chunk)
#log.debug('Downloaded %s to %s from %s' % (filename, fullpath, url))
return fullpath
except Exception as e: # pragma: no cover
print('e %s' % e)
except Exception as err: # pragma: no cover
print('Error downloading file: %s' % err)
#log.exception('Failed to download %s to %s %s' % (url, fullpath, e))

View file

@ -29,8 +29,7 @@ class Video(PlexPartialObject):
self.listType = 'video'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.key = data.attrib.get('key', NA)
self.lastViewedAt = utils.toDatetime(
data.attrib.get('lastViewedAt', NA))
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt', NA))
self.librarySectionID = data.attrib.get('librarySectionID', NA)
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey', NA))
self.summary = data.attrib.get('summary', NA)
@ -110,24 +109,15 @@ class Movie(Video, Playable):
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year', NA))
if self.isFullObject(): # check this
self.collections = [media.Collection(
self.server, e) for e in data if e.tag == media.Collection.TYPE]
self.countries = [media.Country(self.server, e)
for e in data if e.tag == media.Country.TYPE]
self.directors = [media.Director(
self.server, e) for e in data if e.tag == media.Director.TYPE]
self.genres = [media.Genre(self.server, e)
for e in data if e.tag == media.Genre.TYPE]
self.media = [media.Media(self.server, e, self.initpath, self)
for e in data if e.tag == media.Media.TYPE]
self.producers = [media.Producer(
self.server, e) for e in data if e.tag == media.Producer.TYPE]
self.roles = [media.Role(self.server, e)
for e in data if e.tag == media.Role.TYPE]
self.writers = [media.Writer(self.server, e)
for e in data if e.tag == media.Writer.TYPE]
self.fields = [media.Field(e)
for e in data if e.tag == media.Field.TYPE]
self.collections = [media.Collection(self.server, e) for e in data if e.tag == media.Collection.TYPE]
self.countries = [media.Country(self.server, e) for e in data if e.tag == media.Country.TYPE]
self.directors = [media.Director(self.server, e) for e in data if e.tag == media.Director.TYPE]
self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE]
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE]
self.producers = [media.Producer(self.server, e) for e in data if e.tag == media.Producer.TYPE]
self.roles = [media.Role(self.server, e) for e in data if e.tag == media.Role.TYPE]
self.writers = [media.Writer(self.server, e) for e in data if e.tag == media.Writer.TYPE]
self.fields = [media.Field(e) for e in data if e.tag == media.Field.TYPE]
self.videoStreams = utils.findStreams(self.media, 'videostream')
self.audioStreams = utils.findStreams(self.media, 'audiostream')
self.subtitleStreams = utils.findStreams(
@ -145,7 +135,6 @@ class Movie(Video, Playable):
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:
@ -161,17 +150,14 @@ class Movie(Video, Playable):
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
@ -313,10 +299,8 @@ class Show(Video):
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
@ -389,18 +373,14 @@ class Season(Video):
"""
if not title and not episode:
raise TypeError('Missing argument, you need to use title or episode.')
if title:
path = '/library/metadata/%s/children' % self.ratingKey
return utils.findItem(self.server, path, title)
elif episode:
results = [i for i in self.episodes()
if i.seasonNumber == self.index and i.index == episode]
results = [i for i in self.episodes() if i.seasonNumber == self.index and i.index == episode]
if results:
return results[0]
else:
raise NotFound('Couldnt find %s.Season %s Episode %s.' % (self.grandparentTitle, self.index. episode))
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.
@ -437,7 +417,6 @@ class Season(Video):
dl = ep.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
if dl:
downloaded.extend(dl)
return downloaded
@ -465,8 +444,7 @@ class Episode(Video, Playable):
self.grandparentTitle = data.attrib.get('grandparentTitle', NA)
self.guid = data.attrib.get('guid', NA)
self.index = utils.cast(int, data.attrib.get('index', NA))
self.originallyAvailableAt = utils.toDatetime(
data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.parentIndex = data.attrib.get('parentIndex', NA)
self.parentKey = data.attrib.get('parentKey', NA)
self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey', NA))
@ -474,12 +452,9 @@ class Episode(Video, Playable):
self.rating = utils.cast(float, data.attrib.get('rating', NA))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year', NA))
self.directors = [media.Director(self.server, e)
for e in data if e.tag == media.Director.TYPE]
self.media = [media.Media(self.server, e, self.initpath, self)
for e in data if e.tag == media.Media.TYPE]
self.writers = [media.Writer(self.server, e)
for e in data if e.tag == media.Writer.TYPE]
self.directors = [media.Director(self.server, e) for e in data if e.tag == media.Director.TYPE]
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE]
self.writers = [media.Writer(self.server, e) for e in data if e.tag == media.Writer.TYPE]
self.videoStreams = utils.findStreams(self.media, 'videostream')
self.audioStreams = utils.findStreams(self.media, 'audiostream')
self.subtitleStreams = utils.findStreams(self.media, 'subtitlestream')
@ -527,14 +502,13 @@ class Episode(Video, Playable):
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))
return '%s.S%sE%s' % (self.grandparentTitle.replace(' ', '.'),
str(self.seasonNumber).zfill(2), str(self.index).zfill(2))