25 changed files with 618 additions and 180 deletions
@ -0,0 +1,37 @@ |
|||
from couchpotato.core.logger import CPLog |
|||
from axl.axel import Event |
|||
|
|||
log = CPLog(__name__) |
|||
events = {} |
|||
|
|||
def addEvent(name, handler): |
|||
|
|||
if events.get(name): |
|||
e = events[name] |
|||
else: |
|||
e = events[name] = Event(threads = 20, exc_info = True, traceback = True) |
|||
|
|||
e += handler |
|||
|
|||
def removeEvent(name, handler): |
|||
e = events[name] |
|||
e -= handler |
|||
|
|||
def fireEvent(name, *args, **kwargs): |
|||
try: |
|||
e = events[name] |
|||
e.asynchronous = False |
|||
return e(*args, **kwargs) |
|||
except Exception, e: |
|||
log.debug(e) |
|||
|
|||
def fireEventAsync(name, *args, **kwargs): |
|||
try: |
|||
e = events[name] |
|||
e.asynchronous = True |
|||
return e(*args, **kwargs) |
|||
except Exception, e: |
|||
log.debug(e) |
|||
|
|||
def getEvent(name): |
|||
return events[name] |
@ -0,0 +1,37 @@ |
|||
def latinToAscii(unicrap): |
|||
xlate = {0xc0:'A', 0xc1:'A', 0xc2:'A', 0xc3:'A', 0xc4:'A', 0xc5:'A', |
|||
0xc6:'Ae', 0xc7:'C', |
|||
0xc8:'E', 0xc9:'E', 0xca:'E', 0xcb:'E', 0x86:'e', |
|||
0xcc:'I', 0xcd:'I', 0xce:'I', 0xcf:'I', |
|||
0xd0:'Th', 0xd1:'N', |
|||
0xd2:'O', 0xd3:'O', 0xd4:'O', 0xd5:'O', 0xd6:'O', 0xd8:'O', |
|||
0xd9:'U', 0xda:'U', 0xdb:'U', 0xdc:'U', |
|||
0xdd:'Y', 0xde:'th', 0xdf:'ss', |
|||
0xe0:'a', 0xe1:'a', 0xe2:'a', 0xe3:'a', 0xe4:'a', 0xe5:'a', |
|||
0xe6:'ae', 0xe7:'c', |
|||
0xe8:'e', 0xe9:'e', 0xea:'e', 0xeb:'e', |
|||
0xec:'i', 0xed:'i', 0xee:'i', 0xef:'i', |
|||
0xf0:'th', 0xf1:'n', |
|||
0xf2:'o', 0xf3:'o', 0xf4:'o', 0xf5:'o', 0xf6:'o', 0xf8:'o', |
|||
0xf9:'u', 0xfa:'u', 0xfb:'u', 0xfc:'u', |
|||
0xfd:'y', 0xfe:'th', 0xff:'y', |
|||
0xa1:'!', 0xa2:'{cent}', 0xa3:'{pound}', 0xa4:'{currency}', |
|||
0xa5:'{yen}', 0xa6:'|', 0xa7:'{section}', 0xa8:'{umlaut}', |
|||
0xa9:'{C}', 0xaa:'{^a}', 0xab:'<<', 0xac:'{not}', |
|||
0xad:'-', 0xae:'{R}', 0xaf:'_', 0xb0:'{degrees}', |
|||
0xb1:'{+/-}', 0xb2:'{^2}', 0xb3:'{^3}', 0xb4:"'", |
|||
0xb5:'{micro}', 0xb6:'{paragraph}', 0xb7:'*', 0xb8:'{cedilla}', |
|||
0xb9:'{^1}', 0xba:'{^o}', 0xbb:'>>', |
|||
0xbc:'{1/4}', 0xbd:'{1/2}', 0xbe:'{3/4}', 0xbf:'?', |
|||
0xd7:'*', 0xf7:'/' |
|||
} |
|||
|
|||
r = '' |
|||
for i in unicrap: |
|||
if xlate.has_key(ord(i)): |
|||
r += xlate[ord(i)] |
|||
elif ord(i) >= 0x80: |
|||
pass |
|||
else: |
|||
r += str(i) |
|||
return r |
@ -0,0 +1,86 @@ |
|||
from couchpotato.core.event import fireEvent, fireEventAsync |
|||
from couchpotato.core.logger import CPLog |
|||
import glob |
|||
import os |
|||
|
|||
log = CPLog(__name__) |
|||
|
|||
class Loader: |
|||
|
|||
plugins = {} |
|||
providers = {} |
|||
|
|||
modules = {} |
|||
|
|||
def preload(self, root = ''): |
|||
|
|||
self.paths = { |
|||
'plugin' : ('couchpotato.core.plugins', os.path.join(root, 'couchpotato', 'core', 'plugins')), |
|||
'provider' : ('couchpotato.core.providers', os.path.join(root, 'couchpotato', 'core', 'providers')), |
|||
} |
|||
|
|||
for type, tuple in self.paths.iteritems(): |
|||
self.addFromDir(type, tuple[0], tuple[1]) |
|||
|
|||
def run(self): |
|||
did_save = 0 |
|||
|
|||
for module_name, plugin in sorted(self.modules.iteritems()): |
|||
|
|||
# Load module |
|||
m = getattr(self.loadModule(module_name), plugin.get('name')) |
|||
|
|||
log.info("Loading '%s'" % module_name) |
|||
|
|||
# Save default settings for plugin/provider |
|||
did_save += self.loadSettings(m, module_name, save = False) |
|||
|
|||
self.loadPlugins(m, plugin.get('name')) |
|||
|
|||
if did_save: |
|||
fireEvent('settings.save') |
|||
|
|||
def addFromDir(self, type, module, dir): |
|||
|
|||
for file in glob.glob(os.path.join(dir, '*')): |
|||
name = os.path.basename(file) |
|||
if os.path.isdir(os.path.join(dir, name)): |
|||
module_name = '%s.%s' % (module, name) |
|||
self.addModule(type, module_name, name) |
|||
|
|||
def loadSettings(self, module, name, save = True): |
|||
try: |
|||
for section in module.config: |
|||
fireEventAsync('settings.options', section['name'], section) |
|||
options = {} |
|||
for key, option in section['options'].iteritems(): |
|||
options[key] = option['default'] |
|||
fireEventAsync('settings.register', section_name = section['name'], options = options, save = save) |
|||
return True |
|||
except Exception, e: |
|||
log.debug("Failed loading settings for '%s': %s" % (name, e)) |
|||
return False |
|||
|
|||
def loadPlugins(self, module, name): |
|||
try: |
|||
module.start() |
|||
return True |
|||
except Exception, e: |
|||
log.debug("Failed loading plugin '%s': %s" % (name, e)) |
|||
return False |
|||
|
|||
def addModule(self, type, module, name): |
|||
self.modules[module] = { |
|||
'type': type, |
|||
'name': name, |
|||
} |
|||
|
|||
def loadModule(self, name): |
|||
try: |
|||
m = __import__(name) |
|||
splitted = name.split('.') |
|||
for sub in splitted[1:-1]: |
|||
m = getattr(m, sub) |
|||
return m |
|||
except: |
|||
raise |
@ -0,0 +1,4 @@ |
|||
class Plugin(): |
|||
|
|||
def __init__(self): |
|||
pass |
@ -0,0 +1,6 @@ |
|||
from couchpotato.core.plugins.file_browser.main import FileBrowser |
|||
|
|||
def start(): |
|||
return FileBrowser() |
|||
|
|||
config = [] |
@ -0,0 +1,6 @@ |
|||
from couchpotato.core.plugins.movie_add.main import MovieAdd |
|||
|
|||
def start(): |
|||
return MovieAdd() |
|||
|
|||
config = [] |
@ -0,0 +1,30 @@ |
|||
from couchpotato.api import addApiView |
|||
from couchpotato.core.event import getEvent, fireEvent |
|||
from couchpotato.core.plugins.base import Plugin |
|||
from couchpotato.environment import Env |
|||
from flask.helpers import jsonify |
|||
|
|||
class MovieAdd(Plugin): |
|||
|
|||
def __init__(self): |
|||
addApiView('movie.add.search', self.search) |
|||
|
|||
def search(self): |
|||
|
|||
a = Env.getParams() |
|||
|
|||
print fireEvent('provider.movie.search', q = a.get('q')) |
|||
|
|||
movies = [ |
|||
{'id': 1, 'name': 'test'} |
|||
] |
|||
|
|||
return jsonify({ |
|||
'success': True, |
|||
'empty': len(movies) == 0, |
|||
'movies': movies, |
|||
}) |
|||
|
|||
|
|||
def select(self): |
|||
pass |
@ -0,0 +1,57 @@ |
|||
from couchpotato.core.helpers import latinToAscii |
|||
from couchpotato.core.logger import CPLog |
|||
from string import ascii_letters, digits |
|||
import re |
|||
import unicodedata |
|||
import xml.etree.ElementTree as XMLTree |
|||
|
|||
log = CPLog(__name__) |
|||
|
|||
class Provider(): |
|||
|
|||
type = None # movie, nzb, torrent, subtitle, trailer |
|||
timeout = 10 # Default timeout for url requests |
|||
|
|||
def __init__(self): |
|||
pass |
|||
|
|||
def toSaveString(self, string): |
|||
string = latinToAscii(string) |
|||
string = ''.join((c for c in unicodedata.normalize('NFD', unicode(string)) if unicodedata.category(c) != 'Mn')) |
|||
safe_chars = ascii_letters + digits + '_ -.,\':!?' |
|||
r = ''.join([char if char in safe_chars else ' ' for char in string]) |
|||
return re.sub('\s+' , ' ', r) |
|||
|
|||
def toSearchString(self, string): |
|||
string = latinToAscii(string) |
|||
string = ''.join((c for c in unicodedata.normalize('NFD', unicode(string)) if unicodedata.category(c) != 'Mn')) |
|||
safe_chars = ascii_letters + digits + ' \'' |
|||
r = ''.join([char if char in safe_chars else ' ' for char in string]) |
|||
return re.sub('\s+' , ' ', r).replace('\'s', 's').replace('\'', ' ') |
|||
|
|||
def gettextelements(self, xml, path): |
|||
''' Find elements and return tree''' |
|||
|
|||
textelements = [] |
|||
try: |
|||
elements = xml.findall(path) |
|||
except: |
|||
return |
|||
for element in elements: |
|||
textelements.append(element.text) |
|||
return textelements |
|||
|
|||
def gettextelement(self, xml, path): |
|||
''' Find element and return text''' |
|||
|
|||
try: |
|||
return xml.find(path).text |
|||
except: |
|||
return |
|||
|
|||
def getItems(self, data, path = 'channel/item'): |
|||
try: |
|||
return XMLTree.parse(data).findall(path) |
|||
except Exception, e: |
|||
log.error('Error parsing RSS. %s' % e) |
|||
return [] |
@ -0,0 +1,4 @@ |
|||
def start(): |
|||
pass |
|||
|
|||
config = [] |
@ -0,0 +1,83 @@ |
|||
from couchpotato.core.event import addEvent |
|||
from couchpotato.core.logger import CPLog |
|||
from couchpotato.core.providers.base import Provider |
|||
from imdb import IMDb |
|||
|
|||
log = CPLog(__name__) |
|||
|
|||
class IMDB(Provider): |
|||
|
|||
type = 'movie' |
|||
|
|||
def __init__(self): |
|||
|
|||
addEvent('provider.movie.search', self.search) |
|||
|
|||
self.p = IMDb('http') |
|||
|
|||
|
|||
def search(self): |
|||
print 'search' |
|||
|
|||
def conf(self, option): |
|||
return self.config.get('IMDB', option) |
|||
|
|||
def find(self, q, limit = 8, alternative = True): |
|||
''' Find movie by name ''' |
|||
|
|||
log.info('IMDB - Searching for movie: %s' % q) |
|||
|
|||
r = self.p.search_movie(q) |
|||
|
|||
return self.toResults(r, limit) |
|||
|
|||
def toResults(self, r, limit = 8, one = False): |
|||
results = [] |
|||
|
|||
if one: |
|||
new = self.feedItem() |
|||
new.imdb = 'tt' + r.movieID |
|||
new.name = self.toSaveString(r['title']) |
|||
try: |
|||
new.year = r['year'] |
|||
except: |
|||
new.year = '' |
|||
|
|||
return new |
|||
else : |
|||
nr = 0 |
|||
for movie in r: |
|||
results.append(self.toResults(movie, one = True)) |
|||
nr += 1 |
|||
if nr == limit: |
|||
break |
|||
|
|||
return results |
|||
|
|||
def findById(self, id): |
|||
''' Find movie by TheMovieDB ID ''' |
|||
|
|||
return [] |
|||
|
|||
|
|||
def findByImdbId(self, id, details = False): |
|||
''' Find movie by IMDB ID ''' |
|||
|
|||
log.info('IMDB - Searching for movie: %s' % str(id)) |
|||
|
|||
r = self.p.get_movie(id.replace('tt', '')) |
|||
|
|||
if not details: |
|||
return self.toResults(r, one = True) |
|||
else: |
|||
self.p.update(r) |
|||
self.p.update(r, info = 'release dates') |
|||
self.p.update(r, info = 'taglines') |
|||
return r |
|||
|
|||
def get_IMDb_instance(self): |
|||
return IMDb('http') |
|||
|
|||
|
|||
def findReleaseDate(self, movie): |
|||
pass |
@ -0,0 +1,92 @@ |
|||
from __future__ import with_statement |
|||
from couchpotato.core.event import addEvent |
|||
from couchpotato.core.logger import CPLog |
|||
from couchpotato.core.providers.base import Provider |
|||
from couchpotato.environment import Env |
|||
from urllib import quote_plus |
|||
import urllib2 |
|||
|
|||
log = CPLog(__name__) |
|||
|
|||
class TMDB(Provider): |
|||
"""Api for theMovieDb""" |
|||
|
|||
type = 'movie' |
|||
apiUrl = 'http://api.themoviedb.org/2.1' |
|||
imageUrl = 'http://hwcdn.themoviedb.org' |
|||
|
|||
def __init__(self): |
|||
addEvent('provider.movie.search', self.search) |
|||
|
|||
def conf(self, attr): |
|||
return Env.setting(attr, 'TheMovieDB') |
|||
|
|||
def search(self, q, limit = 8, alternative = True): |
|||
''' Find movie by name ''' |
|||
|
|||
if self.isDisabled(): |
|||
return False |
|||
|
|||
log.debug('TheMovieDB - Searching for movie: %s' % q) |
|||
|
|||
url = "%s/%s/%s/xml/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(self.toSearchString(q))) |
|||
|
|||
log.info('Searching: %s' % url) |
|||
|
|||
data = urllib2.urlopen(url) |
|||
|
|||
return self.parseXML(data, limit, alternative = alternative) |
|||
|
|||
def parseXML(self, data, limit, alternative = True): |
|||
if data: |
|||
log.debug('TheMovieDB - Parsing RSS') |
|||
try: |
|||
xml = self.getItems(data, 'movies/movie') |
|||
|
|||
results = [] |
|||
nr = 0 |
|||
for movie in xml: |
|||
id = int(self.gettextelement(movie, "id")) |
|||
|
|||
name = self.gettextelement(movie, "name") |
|||
imdb = self.gettextelement(movie, "imdb_id") |
|||
year = str(self.gettextelement(movie, "released"))[:4] |
|||
|
|||
# 1900 is the same as None |
|||
if year == '1900': |
|||
year = 'None' |
|||
|
|||
results.append({ |
|||
'id': id, |
|||
'name': self.toSaveString(name), |
|||
'imdb': imdb, |
|||
'year': year |
|||
}) |
|||
|
|||
alternativeName = self.gettextelement(movie, "alternative_name") |
|||
if alternativeName and alternative: |
|||
if alternativeName.lower() != name.lower() and alternativeName.lower() != 'none' and alternativeName != None: |
|||
results.append({ |
|||
'id': id, |
|||
'name': self.toSaveString(alternativeName), |
|||
'imdb': imdb, |
|||
'year': year |
|||
}) |
|||
|
|||
nr += 1 |
|||
if nr == limit: |
|||
break |
|||
|
|||
#log.info('TheMovieDB - Found: %s' % results) |
|||
return results |
|||
except SyntaxError: |
|||
log.error('TheMovieDB - Failed to parse XML response from TheMovieDb') |
|||
return False |
|||
|
|||
|
|||
def isDisabled(self): |
|||
if self.conf('api_key') == '': |
|||
log.error('TheMovieDB - No API key provided for TheMovieDB') |
|||
True |
|||
else: |
|||
False |
@ -1,79 +0,0 @@ |
|||
from blinker import signal |
|||
from couchpotato.core.logger import CPLog |
|||
import glob |
|||
import os |
|||
|
|||
log = CPLog(__name__) |
|||
|
|||
class SettingsLoader: |
|||
|
|||
configs = {} |
|||
sections = {} |
|||
|
|||
def __init__(self): |
|||
self.settings_register = signal('settings.register') |
|||
self.settings_save = signal('settings.save') |
|||
|
|||
def load(self, root = ''): |
|||
|
|||
self.paths = { |
|||
'plugins' : ('couchpotato.core.plugins', os.path.join(root, 'couchpotato', 'core', 'plugins')), |
|||
'providers' : ('couchpotato.core.providers', os.path.join(root, 'couchpotato', 'core', 'providers')), |
|||
} |
|||
|
|||
for type, tuple in self.paths.iteritems(): |
|||
self.addFromDir(tuple[0], tuple[1]) |
|||
|
|||
def run(self): |
|||
did_save = 0 |
|||
|
|||
for module, plugin_name in self.configs.iteritems(): |
|||
did_save += self.loadConfig(module, plugin_name, save = False) |
|||
|
|||
if did_save: |
|||
self.settings_save.send() |
|||
|
|||
def addFromDir(self, module, dir): |
|||
|
|||
for file in glob.glob(os.path.join(dir, '*')): |
|||
name = os.path.basename(file) |
|||
if os.path.isdir(os.path.join(dir, name)): |
|||
self.addConfig(module, name) |
|||
|
|||
def loadConfig(self, module, name, save = True): |
|||
module_name = '%s.%s' % (module, name) |
|||
try: |
|||
m = getattr(self.loadModule(module_name), name) |
|||
|
|||
for section in m.config: |
|||
self.addSection(section['name'], section) |
|||
options = {} |
|||
for key, option in section['options'].iteritems(): |
|||
options[key] = option['default'] |
|||
self.settings_register.send(section['name'], options = options, save = save) |
|||
|
|||
return True |
|||
except Exception, e: |
|||
log.error("Failed loading config for %s: %s" % (name, e)) |
|||
return False |
|||
|
|||
def addConfig(self, module, name): |
|||
self.configs[module] = name |
|||
|
|||
def loadModule(self, name): |
|||
try: |
|||
m = __import__(name) |
|||
splitted = name.split('.') |
|||
for sub in splitted[1:-1]: |
|||
m = getattr(m, sub) |
|||
return m |
|||
except: |
|||
raise |
|||
|
|||
def addSection(self, section_name, options): |
|||
self.sections[section_name] = options |
|||
|
|||
def getSections(self): |
|||
return self.sections |
|||
|
|||
settings_loader = SettingsLoader() |
Loading…
Reference in new issue