Merge pull request #2155 from nrgaway/tv_xem

Tv xem
This commit is contained in:
Joel Kåberg
2013-09-16 12:39:15 -07:00
8 changed files with 298 additions and 43 deletions

View File

@@ -82,16 +82,6 @@ class ShowBase(MediaBase):
def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
"""
1. Add Show
2. Add All Episodes
3. Add All Seasons
Notes, not to forget:
- relate parent and children, possible grandparent to grandchild so episodes know it belong to show, etc
- looks like we dont send info to library; it comes later
- change references to plot to description
- change Model to Media
params
{'category_id': u'-1',
'identifier': u'tt1519931',
@@ -101,36 +91,92 @@ class ShowBase(MediaBase):
"""
log.debug("show.add")
# Add show parent to db first
parent = self.addToDatabase(params = params, type = 'show')
# Add show parent to db first; need to update library so maps will be in place (if any)
parent = self.addToDatabase(params = params, update_library = True, type = 'show')
# TODO: add by airdate
# Add by Season/Episode numbers
self.addBySeasonEpisode(parent,
params = params,
force_readd = force_readd,
search_after = search_after,
update_library = update_library,
status_id = status_id
)
def addBySeasonEpisode(self, parent, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
identifier = params.get('id')
# 'tvdb' will always be the master for our purpose. All mapped data can be mapped
# to another source for downloading, but it will always be remapped back to tvdb numbering
# when renamed so media can be used in media players that use tvdb for info provider
#
# This currently means the episode must actually exist in tvdb in order to be found but
# the numbering can be different
# XXX: Fix so we dont have a nested list [0] (fireEvent)
try:
seasons = fireEvent('season.info', identifier = identifier)[0]
except: return None
#master = 'tvdb'
#destination = 'scene'
#destination = 'anidb'
#destination = 'rage'
#destination = 'trakt'
# TODO: auto mode. if anime exists use it. if scene exists use it else use tvdb
# XXX: We should abort adding show, etc if either tvdb or xem is down or we will have incorrent mappings
# I think if tvdb gets error we wont have anydata anyway, but we must make sure XEM returns!!!!
# Only the master should return results here; all other info providers should just return False
# since we are just interested in the structure at this point.
seasons = fireEvent('season.info', merge = True, identifier = identifier)
if seasons is not None:
for season in seasons:
season['title'] = season.get('title', None)
# Make sure we are only dealing with 'tvdb' responses at this point
if season.get('primary_provider', None) != 'thetvdb':
continue
season_id = season.get('id', None)
if season_id is None: continue
season['identifier'] = season_id
season['parent_identifier'] = identifier
self.addToDatabase(params=season, type = "season")
# XXX: Fix so we dont have a nested list [0] (fireEvent)
try:
episodes = fireEvent('episode.info', identifier = identifier, season_identifier = season_id)[0]
except: continue
season_params = {'season_identifier': season_id}
# Calling all info providers; merge your info now for individual season
single_season = fireEvent('season.info', merge = True, identifier = identifier, params = season_params)
single_season['title'] = single_season.get('original_title', None)
single_season['identifier'] = season_id
single_season['parent_identifier'] = identifier
log.info("Adding Season %s" % season_id)
s = self.addToDatabase(params = single_season, type = "season")
episode_params = {'season_identifier': season_id}
episodes = fireEvent('episode.info', merge = True, identifier = identifier, params = episode_params)
if episodes is not None:
for episode in episodes:
episode['title'] = episode.get('titles', None)[0] # XXX. [0] will create exception. FIX!
# Make sure we are only dealing with 'tvdb' responses at this point
if episode.get('primary_provider', None) != 'thetvdb':
continue
episode_id = episode.get('id', None)
if episode_id is None: continue
episode['identifier'] = episode_id
episode['parent_identifier'] = season['identifier']
self.addToDatabase(params=episode, type = "episode")
try:
episode_number = int(episode.get('episodenumber', None))
except (ValueError, TypeError):
continue
try:
absolute_number = int(episode.get('absolute_number', None))
except (ValueError, TypeError):
absolute_number = None
episode_params = {'season_identifier': season_id,
'episode_identifier': episode_id,
'episode': episode_number}
if absolute_number:
episode_params['absolute'] = absolute_number
# Calling all info providers; merge your info now for individual episode
single_episode = fireEvent('episode.info', merge = True, identifier = identifier, params = episode_params)
single_episode['title'] = single_episode.get('original_title', None)
single_episode['identifier'] = episode_id
single_episode['parent_identifier'] = single_season['identifier']
log.info("Adding [%sx%s] %s - %s" % (season_id,
episode_number,
params['title'],
single_episode.get('original_title', '')))
e = self.addToDatabase(params = single_episode, type = "episode")
return parent

View File

@@ -88,8 +88,9 @@ class EpisodeLibraryPlugin(LibraryBase):
if library.status_id == done_status.get('id') and not force:
do_update = False
info = fireEvent('episode.info', merge = True, season_identifier = parent_identifier, \
episode_identifier = identifier)
episode_params = {'season_identifier': parent_identifier,
'episode_identifier': identifier}
info = fireEvent('episode.info', merge = True, params = episode_params)
# Don't need those here
try: del info['in_wanted']
@@ -156,8 +157,7 @@ class EpisodeLibraryPlugin(LibraryBase):
except:
log.debug('Failed to attach to library: %s', traceback.format_exc())
library_dict = library.to_dict(self.default_dict)
library_dict = library.to_dict(self.default_dict)
db.expire_all()
return library_dict

View File

@@ -63,7 +63,6 @@ class SeasonLibraryPlugin(LibraryBase):
handle('library.update.season', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
library_dict = l.to_dict(self.default_dict)
db.expire_all()
return library_dict
@@ -88,8 +87,8 @@ class SeasonLibraryPlugin(LibraryBase):
if library.status_id == done_status.get('id') and not force:
do_update = False
info = fireEvent('season.info', merge = True, identifier = parent_identifier, \
season_identifier = identifier)
season_params = {'season_identifier': identifier}
info = fireEvent('season.info', merge = True, identifier = parent_identifier, params = season_params)
# Don't need those here
try: del info['in_wanted']
@@ -148,13 +147,11 @@ class SeasonLibraryPlugin(LibraryBase):
file_obj = db.query(File).filter_by(id = file_obj.get('id')).one()
library.files.append(file_obj)
db.commit()
break
except:
log.debug('Failed to attach to library: %s', traceback.format_exc())
library_dict = library.to_dict(self.default_dict)
library_dict = library.to_dict(self.default_dict)
db.expire_all()
return library_dict

View File

@@ -57,7 +57,6 @@ class ShowLibraryPlugin(LibraryBase):
handle('library.update.show', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
library_dict = l.to_dict(self.default_dict)
db.expire_all()
return library_dict

View File

@@ -98,11 +98,11 @@ class Dashboard(Plugin):
if len(movie_ids) > 0:
# Get all movie information
movies_raw = db.query(Movie) \
movies_raw = db.query(Media) \
.options(joinedload_all('library.titles')) \
.options(joinedload_all('library.files')) \
.options(joinedload_all('files')) \
.filter(Movie.id.in_(movie_ids)) \
.filter(Media.id.in_(movie_ids)) \
.all()
# Create dict by movie id

View File

@@ -110,13 +110,15 @@ class TheTVDb(ShowProvider):
return result
def getSeasonInfo(self, identifier=None, season_identifier=None):
def getSeasonInfo(self, identifier = None, params = {}):
"""Either return a list of all seasons or a single season by number.
identifier is the show 'id'
"""
if not identifier:
return False
season_identifier = params.get('season_identifier', None)
# season_identifier must contain the 'show id : season number' since there is no tvdb id
# for season and we need a reference to both the show id and season number
if season_identifier:
@@ -147,10 +149,13 @@ class TheTVDb(ShowProvider):
self.setCache(cache_key, result)
return result
def getEpisodeInfo(self, identifier=None, season_identifier=None, episode_identifier=None):
def getEpisodeInfo(self, identifier = None, params = {}):
"""Either return a list of all episodes or a single episode.
If episode_identifer contains an episode number to search for
"""
season_identifier = params.get('season_identifier', None)
episode_identifier = params.get('episode_identifier', None)
if not identifier and season_identifier is None:
return False

View File

@@ -0,0 +1,24 @@
from .main import Xem
def start():
return Xem()
config = [{
'name': 'xem',
'groups': [
{
'tab': 'providers',
'name': 'xem',
'label': 'TheXem',
'hidden': True,
'description': 'Used for all calls to TheXem.',
'options': [
{
'name': 'enabled',
'default': True,
'label': 'Enabled',
},
],
},
],
}]

View File

@@ -0,0 +1,184 @@
from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.info.base import ShowProvider
from couchpotato.core.helpers.encoding import tryUrlencode
import traceback
log = CPLog(__name__)
class Xem(ShowProvider):
'''
Mapping Information
===================
Single
------
You will need the id / identifier of the show e.g. tvdb-id for American Dad! is 73141
the origin is the name of the site/entity the episode, season (and/or absolute) numbers are based on
http://thexem.de/map/single?id=&origin=&episode=&season=&absolute=
episode, season and absolute are all optional but it wont work if you don't provide either episode and season OR absolute in
addition you can provide destination as the name of the wished destination, if not provided it will output all available
When a destination has two or more addresses another entry will be added as _ ... for now the second address gets the index "2"
(the first index is omitted) and so on
http://thexem.de/map/single?id=7529&origin=anidb&season=1&episode=2&destination=trakt
{
"result":"success",
"data":{
"trakt": {"season":1,"episode":3,"absolute":3},
"trakt_2":{"season":1,"episode":4,"absolute":4}
},
"message":"single mapping for 7529 on anidb."
}
All
---
Basically same as "single" just a little easier
The origin address is added into the output too!!
http://thexem.de/map/all?id=7529&origin=anidb
All Names
---------
Get all names xem has to offer
non optional params: origin(an entity string like 'tvdb')
optional params: season, language
- season: a season number or a list like: 1,3,5 or a compare operator like ne,gt,ge,lt,le,eq and a season number. default would
return all
- language: a language string like 'us' or 'jp' default is all
- defaultNames: 1(yes) or 0(no) should the default names be added to the list ? default is 0(no)
http://thexem.de/map/allNames?origin=tvdb&season=le1
{
"result": "success",
"data": {
"248812": ["Dont Trust the Bitch in Apartment 23", "Don't Trust the Bitch in Apartment 23"],
"257571": ["Nazo no Kanojo X"],
"257875": ["Lupin III - Mine Fujiko to Iu Onna", "Lupin III Fujiko to Iu Onna", "Lupin the Third - Mine Fujiko to Iu Onna"]
},
"message": ""
}
'''
def __init__(self):
addEvent('show.info', self.getShowInfo, priority = 5)
addEvent('episode.info', self.getEpisodeInfo, priority = 5)
self.config = {}
self.config['base_url'] = "http://thexem.de"
self.config['url_single'] = u"%(base_url)s/map/single?" % self.config
self.config['url_all'] = u"%(base_url)s/map/all?" % self.config
self.config['url_names'] = u"%(base_url)s/map/names?" % self.config
self.config['url_all_names'] = u"%(base_url)s/map/allNames?" % self.config
# TODO: Also get show aliases (store as titles)
def getShowInfo(self, identifier = None):
if self.isDisabled():
return {}
cache_key = 'xem.cache.%s' % identifier
log.debug('Getting showInfo: %s', cache_key)
result = self.getCache(cache_key) or {}
if result:
return result
# Create season/episode and absolute mappings
url = self.config['url_all'] + "id=%s&origin=tvdb" % tryUrlencode(identifier)
response = self.getJsonData(url)
if response:
if response.get('result') == 'success':
data = response.get('data', None)
result = self._parse(data)
# Create name alias mappings
url = self.config['url_names'] + "id=%s&origin=tvdb" % tryUrlencode(identifier)
response = self.getJsonData(url)
if response:
if response.get('result') == 'success':
data = response.get('data', None)
result.update({'map_names': data})
self.setCache(cache_key, result)
return result
def getEpisodeInfo(self, identifier = None, params = {}):
episode = params.get('episode', None)
if episode is None:
return False
season_identifier = params.get('season_identifier', None)
if season_identifier is None:
return False
episode_identifier = params.get('episode_identifier', None)
absolute = params.get('absolute', None)
# season_identifier must contain the 'show id : season number' since there is no tvdb id
# for season and we need a reference to both the show id and season number
if season_identifier:
try:
identifier, season_identifier = season_identifier.split(':')
season = int(season_identifier)
except: return False
result = self.getShowInfo(identifier)
map = {}
if result:
map_episode = result.get('map_episode', {}).get(season, {}).get(episode, {})
if map_episode:
map.update({'map_episode': map_episode})
if absolute:
map_absolute = result.get('map_absolute', {}).get(absolute, {})
if map_absolute:
map.update({'map_absolute': map_absolute})
map_names = result.get('map_names', {}).get(season, {}).get(episode, {})
if map_names:
map.update({'map_names': map_names})
return map
def _parse(self, data, master = 'tvdb'):
'''parses xem map and returns a custom formatted dict map
To retreive map for scene:
if 'scene' in map['map_episode'][1][1]:
print map['map_episode'][1][1]['scene']['season']
'''
if not isinstance(data, list):
return {}
map = {'map_episode': {}, 'map_absolute': {}}
for maps in data:
origin = maps.pop(master, None)
if origin is None:
continue # No master origin to map to
map.get('map_episode').setdefault(origin['season'], {}).setdefault(origin['episode'], maps.copy())
map.get('map_absolute').setdefault(origin['absolute'], maps.copy())
return map
def isDisabled(self):
if __name__ == '__main__':
return False
if self.conf('enabled'):
return False
else:
return True
#XXX: REMOVE, just for degugging
def main():
"""Simple example of using xem
"""
xem_instance = Xem()
print xem_instance.getShowInfo(identifier=73141) # (American Dad)
if __name__ == '__main__':
main()