Browse Source

Merge branch 'develop' of git://github.com/Boehemyth/CouchPotatoServer into Boehemyth-develop

pull/3289/head
Ruud 11 years ago
parent
commit
21e5f156bb
  1. 9
      couchpotato/core/media/movie/providers/info/_modifier.py
  2. 168
      couchpotato/core/media/movie/providers/info/fanarttv.py
  3. 36
      couchpotato/core/media/movie/providers/info/themoviedb.py
  4. 152
      couchpotato/core/media/movie/providers/metadata/base.py
  5. 145
      couchpotato/core/media/movie/providers/metadata/xbmc.py
  6. 78
      couchpotato/core/notifications/xbmc.py
  7. 110
      libs/fanarttv/__init__.py
  8. 44
      libs/fanarttv/core.py
  9. 15
      libs/fanarttv/errors.py
  10. 46
      libs/fanarttv/immutable.py
  11. 68
      libs/fanarttv/items.py
  12. 103
      libs/fanarttv/movie.py
  13. 80
      libs/fanarttv/music.py
  14. 108
      libs/fanarttv/tv.py

9
couchpotato/core/media/movie/providers/info/_modifier.py

@ -26,7 +26,14 @@ class MovieResultModifier(Plugin):
'backdrop': [],
'poster_original': [],
'backdrop_original': [],
'actors': {}
'actors': {},
'landscape': [],
'logo': [],
'clearart': [],
'discart': [],
'banner': [],
'extrathumbs': [],
'extrafanart': []
},
'runtime': 0,
'plot': '',

168
couchpotato/core/media/movie/providers/info/fanarttv.py

@ -0,0 +1,168 @@
import os
import traceback
from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
from couchpotato.core.media.movie.providers.base import MovieProvider
from couchpotato.core.plugins.quality import QualityPlugin
from libs.fanarttv.movie import Movie
import libs.fanarttv.errors as fanarttv_errors
log = CPLog(__name__)
autoload = 'FanartTV'
class FanartTV(MovieProvider):
MAX_EXTRAFANART = 20
def __init__(self):
addEvent('movie.extraart', self.getArt, priority=2)
# Configure fanarttv API settings
os.environ.setdefault('FANART_APIKEY', self.conf('api_key'))
def getArt(self, identifier):
# FIXME: I believe I should be registering a cache here... I need to look into that.
log.debug("Getting Extra Artwork from Fanart.tv...")
if not identifier:
return {}
images = {}
try:
try:
exists = True
movie = Movie.get(id=identifier)
except (fanarttv_errors.FanartError, IOError):
exists = False
if exists:
images = self._parseMovie(movie, True)
except:
log.error('Failed getting extra art for %s: %s',
(identifier, traceback.format_exc()))
return {}
return images
def _parseMovie(self, movie, isHD):
images = {
'landscape': [],
'logo': [],
'discart': [],
'clearart': [],
'banner': [],
'extrafanart': []
}
images['landscape'] = self._getMultImages(movie.thumbs, 1)
images['banner'] = self._getMultImages(movie.banners, 1)
images['discart'] = self._getMultImages(self._trimDiscs(movie.discs, isHD), 1)
images['clearart'] = self._getMultImages(movie.hdarts, 1)
if len(images['clearart']) is 0:
images['clearart'] = self._getMultImages(movie.arts, 1)
images['logo'] = self._getMultImages(movie.hdlogos, 1)
if len(images['logo']) is 0:
images['logo'] = self._getMultImages(movie.logos, 1)
fanarts = self._getMultImages(movie.backgrounds, self.MAX_EXTRAFANART + 1)
if fanarts:
images['backdrop_original'] = fanarts[0]
images['extrafanart'] = fanarts[1:]
# TODO: Add support for extra backgrounds
#extraFanart = self._getMultImages(movie.backgrounds, -1)
return images
def _trimDiscs(self, discImages, isHD):
'''
Return a subset of discImages based on isHD. If isHD is true, only
bluray disc images will be returned. If isHD is false, only dvd disc
images will be returned. If the resulting list would be an empty list,
then the original list is returned instead.
'''
trimmed = []
for disc in discImages:
if isHD and disc.disc_type == u'bluray':
trimmed.append(disc)
elif not isHD and disc.disc_type == u'dvd':
trimmed.append(disc)
if len(trimmed) is 0:
return discImages
else:
return trimmed
def _getImage(self, images):
image_url = None
highscore = -1
for image in images:
if image.likes > highscore:
highscore = image.likes
image_url = image.url
return image_url
def _getMultImages(self, images, n):
'''
Chooses the best n images and returns them as a list.
If n<0, all images will be returned.
'''
image_urls = []
pool = []
for image in images:
if image.lang == u'en':
pool.append(image)
origPoolSize = len(pool)
while len(pool) > 0 and (n < 0 or origPoolSize - len(pool) < n):
best = None
highscore = -1
for image in pool:
if image.likes > highscore:
highscore = image.likes
best = image
image_urls.append(best.url)
pool.remove(best)
return image_urls
def isDisabled(self):
if self.conf('api_key') == '':
log.error('No API key provided.')
return True
return False
def _determineHD(self, quality):
for qualityDef in QualityPlugin.qualities:
if quality == qualityDef.get('identifier'):
return bool(qualityDef.get('hd'))
return False
config = [{
'name': 'fanarttv',
'groups': [
{
'tab': 'providers',
'name': 'fanarttv',
'label': 'fanart.tv',
'hidden': True,
'description': 'Used for all calls to fanart.tv.',
'options': [
{
'name': 'api_key',
'default': 'd788b4822b9e1f44068026e05557e5d9',
'label': 'API Key',
},
],
},
],
}]

36
couchpotato/core/media/movie/providers/info/themoviedb.py

@ -1,6 +1,6 @@
import traceback
from couchpotato.core.event import addEvent
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import simplifyString, toUnicode, ss
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
@ -13,6 +13,7 @@ autoload = 'TheMovieDb'
class TheMovieDb(MovieProvider):
MAX_EXTRATHUMBS = 4
def __init__(self):
addEvent('movie.info', self.getInfo, priority = 2)
@ -87,6 +88,13 @@ class TheMovieDb(MovieProvider):
except:
log.error('Failed getting info for %s: %s', (identifier, traceback.format_exc()))
# Get extra artwork via Fanart.TV and merge into images dict
try:
extraArt = fireEvent('movie.extraart', identifier)[0]
result['images'] = dict(result['images'].items() + extraArt.items())
except IndexError:
pass
return result
def parseMovie(self, movie, extended = True):
@ -100,13 +108,15 @@ class TheMovieDb(MovieProvider):
poster = self.getImage(movie, type = 'poster', size = 'poster')
poster_original = self.getImage(movie, type = 'poster', size = 'original')
backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original')
extrathumbs = self.getMultImages(movie, type='backdrops', size='original', n=self.MAX_EXTRATHUMBS, skipfirst=True)
images = {
'poster': [poster] if poster else [],
#'backdrop': [backdrop] if backdrop else [],
'poster_original': [poster_original] if poster_original else [],
'backdrop_original': [backdrop_original] if backdrop_original else [],
'actors': {}
'actors': {},
'extrathumbs': extrathumbs
}
# Genres
@ -171,6 +181,28 @@ class TheMovieDb(MovieProvider):
log.debug('Failed getting %s.%s for "%s"', (type, size, ss(str(movie))))
return image_url
def getMultImages(self, movie, type='backdrops', size='original', n=-1, skipfirst=False):
'''
If n < 0, return all images. Otherwise return n images.
If n > len(getattr(movie, type)), then return all images.
If skipfirst is True, then it will skip getattr(movie, type)[0]. This
is because backdrops[0] is typically backdrop.
'''
image_urls = []
try:
images = getattr(movie, type)
if n < 0 or n > len(images):
numImages = len(images)
else:
numImages = n
for i in range(int(skipfirst), numImages + int(skipfirst)):
image_urls.append(images[i].geturl(size=size))
except:
log.debug('Failed getting %i %s.%s for "%s"', (n, type, size, ss(str(movie))))
return image_urls
def isDisabled(self):
if self.conf('api_key') == '':

152
couchpotato/core/media/movie/providers/metadata/base.py

@ -37,76 +37,150 @@ class MovieMetaData(MetaDataBase):
root = os.path.dirname(root_name)
movie_info = group['media'].get('info')
for file_type in ['nfo', 'thumbnail', 'fanart']:
for file_type in ['nfo']:
try:
# Get file path
name = getattr(self, 'get' + file_type.capitalize() + 'Name')(meta_name, root)
if name and (self.conf('meta_' + file_type) or self.conf('meta_' + file_type) is None):
# Get file content
content = getattr(self, 'get' + file_type.capitalize())(movie_info = movie_info, data = group)
if content:
log.debug('Creating %s file: %s', (file_type, name))
if os.path.isfile(content):
content = sp(content)
name = sp(name)
shutil.copy2(content, name)
shutil.copyfile(content, name)
# Try and copy stats seperately
try: shutil.copystat(content, name)
except: pass
else:
self.createFile(name, content)
group['renamed_files'].append(name)
try:
os.chmod(sp(name), Env.getPermission('file'))
except:
log.debug('Failed setting permissions for %s: %s', (name, traceback.format_exc()))
self._createType(meta_name, root, movie_info, group, file_type, 0)
except:
log.error('Unable to create %s file: %s', ('nfo', traceback.format_exc()))
for file_type in ['thumbnail', 'fanart', 'banner', 'discart', 'logo', 'clearart', 'landscape', 'extrathumbs', 'extrafanart']:
try:
if file_type == 'thumbnail':
numImages = len(movie_info['images']['poster_original'])
elif file_type == 'fanart':
numImages = len(movie_info['images']['backdrop_original'])
else:
numImages = len(movie_info['images'][file_type])
for i in range(numImages):
self._createType(meta_name, root, movie_info, group, file_type, i)
except:
log.error('Unable to create %s file: %s', (file_type, traceback.format_exc()))
def _createType(self, meta_name, root, movie_info, group, file_type, i):# Get file path
name = getattr(self, 'get' + file_type.capitalize() + 'Name')(meta_name, root, i)
if name and (self.conf('meta_' + file_type) or self.conf('meta_' + file_type) is None):
# Get file content
content = getattr(self, 'get' + file_type.capitalize())(movie_info=movie_info, data=group, i=i)
if content:
log.debug('Creating %s file: %s', (file_type, name))
if os.path.isfile(content):
content = sp(content)
name = sp(name)
if not os.path.exists(os.path.dirname(name)):
os.makedirs(os.path.dirname(name))
shutil.copy2(content, name)
shutil.copyfile(content, name)
# Try and copy stats seperately
try: shutil.copystat(content, name)
except: pass
else:
self.createFile(name, content)
group['renamed_files'].append(name)
try:
os.chmod(sp(name), Env.getPermission('file'))
except:
log.debug('Failed setting permissions for %s: %s', (name, traceback.format_exc()))
def getRootName(self, data = None):
if not data: data = {}
return os.path.join(data['destination_dir'], data['filename'])
def getFanartName(self, name, root):
def getFanartName(self, name, root, i):
return
def getThumbnailName(self, name, root, i):
return
def getBannerName(self, name, root, i):
return
def getClearartName(self, name, root, i):
return
def getLogoName(self, name, root, i):
return
def getDiscartName(self, name, root, i):
return
def getLandscapeName(self, name, root, i):
return
def getExtrathumbsName(self, name, root, i):
return
def getThumbnailName(self, name, root):
def getExtrafanartName(self, name, root, i):
return
def getNfoName(self, name, root):
def getNfoName(self, name, root, i):
return
def getNfo(self, movie_info = None, data = None):
def getNfo(self, movie_info=None, data=None, i=0):
if not data: data = {}
if not movie_info: movie_info = {}
def getThumbnail(self, movie_info = None, data = None, wanted_file_type = 'poster_original'):
def getThumbnail(self, movie_info = None, data = None, wanted_file_type = 'poster_original', i = 0):
if not data: data = {}
if not movie_info: movie_info = {}
# See if it is in current files
files = data['media'].get('files')
if files.get('image_' + wanted_file_type):
if os.path.isfile(files['image_' + wanted_file_type][0]):
return files['image_' + wanted_file_type][0]
if os.path.isfile(files['image_' + wanted_file_type][i]):
return files['image_' + wanted_file_type][i]
# Download using existing info
try:
images = movie_info['images'][wanted_file_type]
file_path = fireEvent('file.download', url = images[0], single = True)
file_path = fireEvent('file.download', url = images[i], single = True)
return file_path
except:
pass
def getFanart(self, movie_info = None, data = None):
def getFanart(self, movie_info = None, data = None, i = 0):
if not data: data = {}
if not movie_info: movie_info = {}
return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='backdrop_original', i=i)
def getBanner(self, movie_info=None, data=None, i=0):
if not data: data = {}
if not movie_info: movie_info = {}
return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='banner', i=i)
def getClearart(self, movie_info=None, data=None, i=0):
if not data: data = {}
if not movie_info: movie_info = {}
return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='clearart', i=i)
def getLogo(self, movie_info=None, data=None, i=0):
if not data: data = {}
if not movie_info: movie_info = {}
return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='logo', i=i)
def getDiscart(self, movie_info=None, data=None, i=0):
if not data: data = {}
if not movie_info: movie_info = {}
return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='discart', i=i)
def getLandscape(self, movie_info=None, data=None, i=0):
if not data: data = {}
if not movie_info: movie_info = {}
return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='landscape', i=i)
def getExtrathumbs(self, movie_info=None, data=None, i=0):
if not data: data = {}
if not movie_info: movie_info = {}
return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='extrathumbs', i=i)
def getExtrafanart(self, movie_info=None, data=None, i=0):
if not data: data = {}
if not movie_info: movie_info = {}
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'backdrop_original')
return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='extrafanart', i=i)

145
couchpotato/core/media/movie/providers/metadata/xbmc.py

@ -17,19 +17,43 @@ autoload = 'XBMC'
class XBMC(MovieMetaData):
def getFanartName(self, name, root):
def getFanartName(self, name, root, i):
return self.createMetaName(self.conf('meta_fanart_name'), name, root)
def getThumbnailName(self, name, root):
def getThumbnailName(self, name, root, i):
return self.createMetaName(self.conf('meta_thumbnail_name'), name, root)
def getNfoName(self, name, root):
def getNfoName(self, name, root, i):
return self.createMetaName(self.conf('meta_nfo_name'), name, root)
def getBannerName(self, name, root, i):
return self.createMetaName(self.conf('meta_banner_name'), name, root)
def getClearartName(self, name, root, i):
return self.createMetaName(self.conf('meta_clearart_name'), name, root)
def getLogoName(self, name, root, i):
return self.createMetaName(self.conf('meta_logo_name'), name, root)
def getDiscartName(self, name, root, i):
return self.createMetaName(self.conf('meta_discart_name'), name, root)
def getLandscapeName(self, name, root, i):
return self.createMetaName(self.conf('meta_landscape_name'), name, root)
def getExtrathumbsName(self, name, root, i):
return self.createMetaNameMult(self.conf('meta_extrathumbs_name'), name, root, i)
def getExtrafanartName(self, name, root, i):
return self.createMetaNameMult(self.conf('meta_extrafanart_name'), name, root, i)
def createMetaName(self, basename, name, root):
return os.path.join(root, basename.replace('%s', name))
def getNfo(self, movie_info = None, data = None):
def createMetaNameMult(self, basename, name, root, i):
return os.path.join(root, basename.replace('%s', name).replace('%i', str(i + 1)))
def getNfo(self, movie_info=None, data=None, i=0):
if not data: data = {}
if not movie_info: movie_info = {}
@ -133,6 +157,35 @@ class XBMC(MovieMetaData):
for image_url in movie_info['images']['backdrop_original']:
image = SubElement(fanart, 'thumb')
image.text = toUnicode(image_url)
banner = SubElement(nfoxml, 'banner')
for image_url in movie_info['images']['banner']:
image = SubElement(banner, 'thumb')
image.text = toUnicode(image_url)
discart = SubElement(nfoxml, 'discart')
for image_url in movie_info['images']['discart']:
image = SubElement(discart, 'thumb')
image.text = toUnicode(image_url)
logo = SubElement(nfoxml, 'logo')
for image_url in movie_info['images']['logo']:
image = SubElement(logo, 'thumb')
image.text = toUnicode(image_url)
clearart = SubElement(nfoxml, 'clearart')
for image_url in movie_info['images']['clearart']:
image = SubElement(clearart, 'thumb')
image.text = toUnicode(image_url)
landscape = SubElement(nfoxml, 'landscape')
for image_url in movie_info['images']['landscape']:
image = SubElement(landscape, 'thumb')
image.text = toUnicode(image_url)
extrathumb = SubElement(nfoxml, 'extrathumb')
for image_url in movie_info['images']['extrathumbs']:
image = SubElement(extrathumb, 'thumb')
image.text = toUnicode(image_url)
extrafanart = SubElement(nfoxml, 'extrafanart')
for image_url in movie_info['images']['extrafanart']:
image = SubElement(extrafanart, 'thumb')
image.text = toUnicode(image_url)
# Add trailer if found
trailer_found = False
@ -239,6 +292,90 @@ config = [{
'default': '%s.tbn',
'advanced': True,
},
{
'name': 'meta_banner',
'label': 'Banner',
'default': True,
'type': 'bool'
},
{
'name': 'meta_banner_name',
'label': 'Banner filename',
'default': 'banner.jpg',
'advanced': True,
},
{
'name': 'meta_clearart',
'label': 'ClearArt',
'default': True,
'type': 'bool'
},
{
'name': 'meta_clearart_name',
'label': 'ClearArt filename',
'default': 'clearart.png',
'advanced': True,
},
{
'name': 'meta_disc',
'label': 'DiscArt',
'default': True,
'type': 'bool'
},
{
'name': 'meta_discart_name',
'label': 'DiscArt filename',
'default': 'disc.png',
'advanced': True,
},
{
'name': 'meta_landscape',
'label': 'Landscape',
'default': True,
'type': 'bool'
},
{
'name': 'meta_landscape_name',
'label': 'Landscape filename',
'default': 'landscape.jpg',
'advanced': True,
},
{
'name': 'meta_logo',
'label': 'ClearLogo',
'default': True,
'type': 'bool'
},
{
'name': 'meta_logo_name',
'label': 'ClearLogo filename',
'default': 'logo.png',
'advanced': True,
},
{
'name': 'meta_extrathumbs',
'label': 'Extrathumbs',
'default': True,
'type': 'bool'
},
{
'name': 'meta_extrathumbs_name',
'label': 'Extrathumbs filename (%i is the image number, and must be included to have multiple images).',
'default': 'extrathumbs/thumb%i.jpg',
'advanced': True
},
{
'name': 'meta_extrafanart',
'lavel': 'Extrafanart',
'default': True,
'type': 'bool'
},
{
'name': 'meta_extrafanart_name',
'label': 'Extrafanart filename (%i is the image number, and must be included to have multiple images).',
'default': 'extrafanart/extrafanart%i.jpg',
'advanced': True
}
],
},
],

78
couchpotato/core/notifications/xbmc.py

@ -3,6 +3,8 @@ import json
import socket
import traceback
import urllib
import time
import os
from couchpotato.core.helpers.variable import splitString, getTitle
from couchpotato.core.logger import CPLog
@ -36,7 +38,7 @@ class XBMC(Notification):
if self.use_json_notifications.get(host):
calls = [
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}),
('GUI.ShowNotification', None, {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}),
]
if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
@ -44,7 +46,7 @@ class XBMC(Notification):
if not self.conf('force_full_scan') and (self.conf('remote_dir_scan') or socket.getfqdn('localhost') == socket.getfqdn(host.split(':')[0])):
param = {'directory': data['destination_dir']}
calls.append(('VideoLibrary.Scan', param))
calls.append(('VideoLibrary.Scan', None, param))
max_successful += len(calls)
response = self.request(host, calls)
@ -66,6 +68,50 @@ class XBMC(Notification):
except:
log.error('Failed parsing results: %s', traceback.format_exc())
if self.conf('run_artwork_downloader') and data and self.use_json_notifications.get(host):
time.sleep(self.conf('run_artwork_downloader_delay'))
if self.conf('force_full_scan'):
calls = [('Addons.ExecuteAddon', None, {'addonid': 'script.artwork.downloader'})]
max_successful += len(calls)
response = self.request(host, calls)
try:
if response[0].get('result') and result['result'] == 'OK':
successful += 1
elif response[0].get('error'):
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
except:
log.error('Failed parsing results: %s', traceback.format_exc())
else:
calls = [('VideoLibrary.GetMovies', 'libMovies', {"filter":{"field": "title", "operator": "is", "value": data['media']['title'], "year": data['media']['info']['year']}})]
max_successful += len(calls)
response = self.request(host, calls)
dbid = None
try:
if response[0].get('result'):
successful += 1
dbid = response[0]['result']['movies'][-1]['movieid']
elif response[0].get('error'):
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
except:
log.error('Failed parsing results: %s', traceback.format_exc())
if dbid is not None:
calls = [('Addons.ExecuteAddon', None, {'addonid': 'script.artwork.downloader', 'params':{'media_type': 'movie', 'dbid': str(dbid)}})]
max_successful += len(calls)
response = self.request(host, calls)
try:
if response[0].get('result') and result['result'] == 'OK':
successful += 1
elif response[0].get('error'):
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
except:
log.error('Failed parsing results: %s', traceback.format_exc())
return successful == max_successful
@ -75,7 +121,7 @@ class XBMC(Notification):
# XBMC JSON-RPC version request
response = self.request(host, [
('JSONRPC.Version', {})
('JSONRPC.Version', None, {})
])
for result in response:
if result.get('result') and type(result['result']['version']).__name__ == 'int':
@ -112,7 +158,7 @@ class XBMC(Notification):
self.use_json_notifications[host] = True
# send the text message
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})])
resp = self.request(host, [('GUI.ShowNotification', None, {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})])
for r in resp:
if r.get('result') and r['result'] == 'OK':
log.debug('Message delivered successfully!')
@ -184,12 +230,16 @@ class XBMC(Notification):
data = []
for req in do_requests:
method, kwargs = req
method, id, kwargs = req
if id is None:
id = method
data.append({
'method': method,
'params': kwargs,
'jsonrpc': '2.0',
'id': method,
'id': id,
})
data = json.dumps(data)
@ -273,6 +323,22 @@ config = [{
'advanced': True,
'description': 'Also send message when movie is snatched.',
},
{
'name': 'run_artwork_downloader',
'label': 'Run the Artwork Downloader',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Runs the Artwork Downloader script to initialize/download artwork. (Requires the Artwork Downloader addon and XBMC Frodo or later)'
},
{
'name': 'run_artwork_downloader_delay',
'label': 'Artwork Downloader delay',
'default': 5,
'type': 'int',
'advanced': True,
'description': 'Number of seconds to wait to start the Artwork Downloader script after notifying XBMC.',
},
],
}
],

110
libs/fanarttv/__init__.py

@ -0,0 +1,110 @@
__author__ = 'Andrea De Marco <24erre@gmail.com>'
__version__ = '1.4.0'
__classifiers__ = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Software Development :: Libraries',
]
__copyright__ = "2012, %s " % __author__
__license__ = """
Copyright %s.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.
See the License for the specific language governing permissions and
limitations under the License.
""" % __copyright__
__docformat__ = 'restructuredtext en'
__doc__ = """
:abstract: Python interface to fanart.tv API
:version: %s
:author: %s
:contact: http://z4r.github.com/
:date: 2012-04-04
:copyright: %s
""" % (__version__, __author__, __license__)
def values(obj):
return [v for k, v in obj.__dict__.iteritems() if not k.startswith('_')]
BASEURL = 'http://api.fanart.tv/webservice'
class FORMAT(object):
JSON = 'JSON'
XML = 'XML'
PHP = 'PHP'
class WS(object):
MUSIC = 'artist'
MOVIE = 'movie'
TV = 'series'
class TYPE(object):
ALL = 'all'
class TV(object):
ART = 'clearart'
LOGO = 'clearlogo'
CHARACTER = 'characterart'
THUMB = 'tvthumb'
SEASONTHUMB = 'seasonthumb'
BACKGROUND = 'showbackground'
HDLOGO = 'hdtvlogo'
HDART = 'hdclearart'
POSTER = 'tvposter'
BANNER = 'tvbanner'
class MUSIC(object):
DISC = 'cdart'
LOGO = 'musiclogo'
BACKGROUND = 'artistbackground'
COVER = 'albumcover'
THUMB = 'artistthumb'
class MOVIE(object):
ART = 'movieart'
LOGO = 'movielogo'
DISC = 'moviedisc'
POSTER = 'movieposter'
BACKGROUND = 'moviebackground'
HDLOGO = 'hdmovielogo'
HDART = 'hdmovieclearart'
BANNER = 'moviebanner'
THUMB = 'moviethumb'
class SORT(object):
POPULAR = 1
NEWEST = 2
OLDEST = 3
class LIMIT(object):
ONE = 1
ALL = 2
FORMAT_LIST = values(FORMAT)
WS_LIST = values(WS)
TYPE_LIST = values(TYPE.MUSIC) + values(TYPE.TV) + values(TYPE.MOVIE) + [TYPE.ALL]
MUSIC_TYPE_LIST = values(TYPE.MUSIC) + [TYPE.ALL]
TV_TYPE_LIST = values(TYPE.TV) + [TYPE.ALL]
MOVIE_TYPE_LIST = values(TYPE.MOVIE) + [TYPE.ALL]
SORT_LIST = values(SORT)
LIMIT_LIST = values(LIMIT)

44
libs/fanarttv/core.py

@ -0,0 +1,44 @@
import libs.requests as requests
import libs.fanarttv as fanart
from libs.fanarttv.errors import RequestFanartError, ResponseFanartError
class Request(object):
def __init__(self, apikey, id, ws, type=None, sort=None, limit=None):
self._apikey = apikey
self._id = id
self._ws = ws
self._type = type or fanart.TYPE.ALL
self._sort = sort or fanart.SORT.POPULAR
self._limit = limit or fanart.LIMIT.ALL
self.validate()
self._response = None
def validate(self):
for attribute_name in ('ws', 'type', 'sort', 'limit'):
attribute = getattr(self, '_' + attribute_name)
choices = getattr(fanart, attribute_name.upper() + '_LIST')
if attribute not in choices:
raise RequestFanartError('Not allowed {0}: {1} [{2}]'.format(attribute_name, attribute, ', '.join(choices)))
def __str__(self):
return '/'.join(map(str, [
fanart.BASEURL,
self._ws,
self._apikey,
self._id,
fanart.FORMAT.JSON,
self._type,
self._sort,
self._limit,
]))
def response(self):
try:
response = requests.get(str(self))
rjson = response.json()
if not isinstance(rjson, dict):
raise Exception(response.text)
return rjson
except Exception as e:
raise ResponseFanartError(str(e))

15
libs/fanarttv/errors.py

@ -0,0 +1,15 @@
class FanartError(Exception):
def __str__(self):
return ', '.join(map(str, self.args))
def __repr__(self):
name = self.__class__.__name__
return '%s%r' % (name, self.args)
class ResponseFanartError(FanartError):
pass
class RequestFanartError(FanartError):
pass

46
libs/fanarttv/immutable.py

@ -0,0 +1,46 @@
class Immutable(object):
_mutable = False
def __setattr__(self, name, value):
if self._mutable or name == '_mutable':
super(Immutable, self).__setattr__(name, value)
else:
raise TypeError("Can't modify immutable instance")
def __delattr__(self, name):
if self._mutable:
super(Immutable, self).__delattr__(name)
else:
raise TypeError("Can't modify immutable instance")
def __eq__(self, other):
return hash(self) == hash(other)
def __hash__(self):
return hash(repr(self))
def __repr__(self):
return '%s(%s)' % (
self.__class__.__name__,
', '.join(['{0}={1}'.format(k, repr(v)) for k, v in self])
)
def __iter__(self):
l = self.__dict__.keys()
l.sort()
for k in l:
if not k.startswith('_'):
yield k, getattr(self, k)
@staticmethod
def mutablemethod(f):
def func(self, *args, **kwargs):
if isinstance(self, Immutable):
old_mutable = self._mutable
self._mutable = True
res = f(self, *args, **kwargs)
self._mutable = old_mutable
else:
res = f(self, *args, **kwargs)
return res
return func

68
libs/fanarttv/items.py

@ -0,0 +1,68 @@
import json
import os
import libs.requests as requests
from libs.fanarttv.core import Request
from libs.fanarttv.immutable import Immutable
class LeafItem(Immutable):
KEY = NotImplemented
@Immutable.mutablemethod
def __init__(self, id, url, likes):
self.id = int(id)
self.url = url
self.likes = int(likes)
self._content = None
@classmethod
def from_dict(cls, resource):
return cls(**dict([(str(k), v) for k, v in resource.iteritems()]))
@classmethod
def extract(cls, resource):
return [cls.from_dict(i) for i in resource.get(cls.KEY, {})]
@Immutable.mutablemethod
def content(self):
if not self._content:
self._content = requests.get(self.url).content
return self._content
def __str__(self):
return self.url
class ResourceItem(Immutable):
WS = NotImplemented
request_cls = Request
@classmethod
def from_dict(cls, map):
raise NotImplementedError
@classmethod
def get(cls, id):
map = cls.request_cls(
apikey=os.environ.get('FANART_APIKEY'),
id=id,
ws=cls.WS
).response()
return cls.from_dict(map)
def json(self, **kw):
return json.dumps(
self,
default=lambda o: dict([(k, v) for k, v in o.__dict__.items() if not k.startswith('_')]),
**kw
)
class CollectableItem(Immutable):
@classmethod
def from_dict(cls, key, map):
raise NotImplementedError
@classmethod
def collection_from_dict(cls, map):
return [cls.from_dict(k, v) for k, v in map.iteritems()]

103
libs/fanarttv/movie.py

@ -0,0 +1,103 @@
import libs.fanarttv as fanart
from libs.fanarttv.items import LeafItem, Immutable, ResourceItem
__all__ = (
'ArtItem',
'DiscItem',
'LogoItem',
'PosterItem',
'BackgroundItem',
'HdLogoItem',
'HdArtItem',
'BannerItem',
'ThumbItem',
'Movie',
)
class MovieItem(LeafItem):
@Immutable.mutablemethod
def __init__(self, id, url, likes, lang):
super(MovieItem, self).__init__(id, url, likes)
self.lang = lang
class DiscItem(MovieItem):
KEY = fanart.TYPE.MOVIE.DISC
@Immutable.mutablemethod
def __init__(self, id, url, likes, lang, disc, disc_type):
super(DiscItem, self).__init__(id, url, likes, lang)
self.disc = int(disc)
self.disc_type = disc_type
class ArtItem(MovieItem):
KEY = fanart.TYPE.MOVIE.ART
class LogoItem(MovieItem):
KEY = fanart.TYPE.MOVIE.LOGO
class PosterItem(MovieItem):
KEY = fanart.TYPE.MOVIE.POSTER
class BackgroundItem(MovieItem):
KEY = fanart.TYPE.MOVIE.BACKGROUND
class HdLogoItem(MovieItem):
KEY = fanart.TYPE.MOVIE.HDLOGO
class HdArtItem(MovieItem):
KEY = fanart.TYPE.MOVIE.HDART
class BannerItem(MovieItem):
KEY = fanart.TYPE.MOVIE.BANNER
class ThumbItem(MovieItem):
KEY = fanart.TYPE.MOVIE.THUMB
class Movie(ResourceItem):
WS = fanart.WS.MOVIE
@Immutable.mutablemethod
def __init__(self, name, imdbid, tmdbid, arts, logos, discs, posters, backgrounds, hdlogos, hdarts,
banners, thumbs):
self.name = name
self.imdbid = imdbid
self.tmdbid = tmdbid
self.arts = arts
self.posters = posters
self.logos = logos
self.discs = discs
self.backgrounds = backgrounds
self.hdlogos = hdlogos
self.hdarts = hdarts
self.banners = banners
self.thumbs = thumbs
@classmethod
def from_dict(cls, resource):
assert len(resource) == 1, 'Bad Format Map'
name, resource = resource.items()[0]
return cls(
name=name,
imdbid=resource['imdb_id'],
tmdbid=resource['tmdb_id'],
arts=ArtItem.extract(resource),
logos=LogoItem.extract(resource),
discs=DiscItem.extract(resource),
posters=PosterItem.extract(resource),
backgrounds=BackgroundItem.extract(resource),
hdlogos=HdLogoItem.extract(resource),
hdarts=HdArtItem.extract(resource),
banners=BannerItem.extract(resource),
thumbs=ThumbItem.extract(resource),
)

80
libs/fanarttv/music.py

@ -0,0 +1,80 @@
from libs.fanarttv.items import Immutable, LeafItem, ResourceItem, CollectableItem
import libs.fanarttv as fanart
__all__ = (
'BackgroundItem',
'CoverItem',
'LogoItem',
'ThumbItem',
'DiscItem',
'Artist',
'Album',
)
class BackgroundItem(LeafItem):
KEY = fanart.TYPE.MUSIC.BACKGROUND
class CoverItem(LeafItem):
KEY = fanart.TYPE.MUSIC.COVER
class LogoItem(LeafItem):
KEY = fanart.TYPE.MUSIC.LOGO
class ThumbItem(LeafItem):
KEY = fanart.TYPE.MUSIC.THUMB
class DiscItem(LeafItem):
KEY = fanart.TYPE.MUSIC.DISC
@Immutable.mutablemethod
def __init__(self, id, url, likes, disc, size):
super(DiscItem, self).__init__(id, url, likes)
self.disc = int(disc)
self.size = int(size)
class Artist(ResourceItem):
WS = fanart.WS.MUSIC
@Immutable.mutablemethod
def __init__(self, name, mbid, albums, backgrounds, logos, thumbs):
self.name = name
self.mbid = mbid
self.albums = albums
self.backgrounds = backgrounds
self.logos = logos
self.thumbs = thumbs
@classmethod
def from_dict(cls, resource):
assert len(resource) == 1, 'Bad Format Map'
name, resource = resource.items()[0]
return cls(
name=name,
mbid=resource['mbid_id'],
albums=Album.collection_from_dict(resource.get('albums', {})),
backgrounds=BackgroundItem.extract(resource),
thumbs=ThumbItem.extract(resource),
logos=LogoItem.extract(resource),
)
class Album(CollectableItem):
@Immutable.mutablemethod
def __init__(self, mbid, covers, arts):
self.mbid = mbid
self.covers = covers
self.arts = arts
@classmethod
def from_dict(cls, key, resource):
return cls(
mbid=key,
covers=CoverItem.extract(resource),
arts=DiscItem.extract(resource),
)

108
libs/fanarttv/tv.py

@ -0,0 +1,108 @@
import libs.fanarttv as fanart
from libs.fanarttv.items import LeafItem, Immutable, ResourceItem
__all__ = (
'CharacterItem',
'ArtItem',
'LogoItem',
'BackgroundItem',
'SeasonItem',
'ThumbItem',
'HdLogoItem',
'HdArtItem',
'PosterItem',
'BannerItem',
'TvShow',
)
class TvItem(LeafItem):
@Immutable.mutablemethod
def __init__(self, id, url, likes, lang):
super(TvItem, self).__init__(id, url, likes)
self.lang = lang
class SeasonedTvItem(TvItem):
@Immutable.mutablemethod
def __init__(self, id, url, likes, lang, season):
super(SeasonedTvItem, self).__init__(id, url, likes, lang)
self.season = 0 if season == 'all' else int(season or 0)
class CharacterItem(TvItem):
KEY = fanart.TYPE.TV.CHARACTER
class ArtItem(TvItem):
KEY = fanart.TYPE.TV.ART
class LogoItem(TvItem):
KEY = fanart.TYPE.TV.LOGO
class BackgroundItem(SeasonedTvItem):
KEY = fanart.TYPE.TV.BACKGROUND
class SeasonItem(SeasonedTvItem):
KEY = fanart.TYPE.TV.SEASONTHUMB
class ThumbItem(TvItem):
KEY = fanart.TYPE.TV.THUMB
class HdLogoItem(TvItem):
KEY = fanart.TYPE.TV.HDLOGO
class HdArtItem(TvItem):
KEY = fanart.TYPE.TV.HDART
class PosterItem(TvItem):
KEY = fanart.TYPE.TV.POSTER
class BannerItem(TvItem):
KEY = fanart.TYPE.TV.BANNER
class TvShow(ResourceItem):
WS = fanart.WS.TV
@Immutable.mutablemethod
def __init__(self, name, tvdbid, backgrounds, characters, arts, logos, seasons, thumbs, hdlogos, hdarts, posters,
banners):
self.name = name
self.tvdbid = tvdbid
self.backgrounds = backgrounds
self.characters = characters
self.arts = arts
self.logos = logos
self.seasons = seasons
self.thumbs = thumbs
self.hdlogos = hdlogos
self.hdarts = hdarts
self.posters = posters
self.banners = banners
@classmethod
def from_dict(cls, resource):
assert len(resource) == 1, 'Bad Format Map'
name, resource = resource.items()[0]
return cls(
name=name,
tvdbid=resource['thetvdb_id'],
backgrounds=BackgroundItem.extract(resource),
characters=CharacterItem.extract(resource),
arts=ArtItem.extract(resource),
logos=LogoItem.extract(resource),
seasons=SeasonItem.extract(resource),
thumbs=ThumbItem.extract(resource),
hdlogos=HdLogoItem.extract(resource),
hdarts=HdArtItem.extract(resource),
posters=PosterItem.extract(resource),
banners=BannerItem.extract(resource),
)
Loading…
Cancel
Save