Movie add
JSONP
This commit is contained in:
1 parent
f9523444f4
commit
e39de40d2d
24 files changed
+871
-5029
No files matched your search
@@ -1,5 +1,6 @@
|
||||
from couchpotato.core.logger import CPLog
|
||||
from axl.axel import Event
|
||||
from couchpotato.core.logger import CPLog
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
events = {}
|
||||
@@ -21,7 +22,17 @@ def fireEvent(name, *args, **kwargs):
|
||||
try:
|
||||
e = events[name]
|
||||
e.asynchronous = False
|
||||
return e(*args, **kwargs)
|
||||
result = e(*args, **kwargs)
|
||||
|
||||
results = []
|
||||
for r in result:
|
||||
if r[0] == True:
|
||||
results.append(r[1])
|
||||
else:
|
||||
etype, value, tb = r[1]
|
||||
log.debug(''.join(traceback.format_exception(etype, value, tb)))
|
||||
|
||||
return results
|
||||
except Exception, e:
|
||||
log.debug(e)
|
||||
|
||||
@@ -29,7 +40,8 @@ def fireEventAsync(name, *args, **kwargs):
|
||||
try:
|
||||
e = events[name]
|
||||
e.asynchronous = True
|
||||
return e(*args, **kwargs)
|
||||
e(*args, **kwargs)
|
||||
return True
|
||||
except Exception, e:
|
||||
log.debug(e)
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
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 @@
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from couchpotato.core.logger import CPLog
|
||||
from string import ascii_letters, digits
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
def toSafeString(original):
|
||||
valid_chars = "-_.() %s%s" % (ascii_letters, digits)
|
||||
cleanedFilename = unicodedata.normalize('NFKD', toUnicode(original)).encode('ASCII', 'ignore')
|
||||
return ''.join(c for c in cleanedFilename if c in valid_chars)
|
||||
|
||||
def simplifyString(original):
|
||||
string = toSafeString(original)
|
||||
split = re.split('\W+', string.lower())
|
||||
return toUnicode(' '.join(split))
|
||||
|
||||
def toUnicode(original, *args):
|
||||
try:
|
||||
if type(original) is unicode:
|
||||
return original
|
||||
else:
|
||||
return unicode(original, *args)
|
||||
except UnicodeDecodeError:
|
||||
ascii_text = str(original).encode('string_escape')
|
||||
return unicode(ascii_text)
|
||||
@@ -0,0 +1,22 @@
|
||||
from flask.globals import current_app
|
||||
from flask.helpers import json, jsonify
|
||||
import flask
|
||||
|
||||
|
||||
def getParams():
|
||||
return getattr(flask.request, 'args')
|
||||
|
||||
def getParam(attr, default = None):
|
||||
return getattr(flask.request, 'args').get(attr, default)
|
||||
|
||||
def padded_jsonify(callback, *args, **kwargs):
|
||||
content = str(callback) + '(' + json.dumps(dict(*args, **kwargs)) + ')'
|
||||
return current_app.response_class(content, mimetype = 'text/javascript')
|
||||
|
||||
def jsonified(*args, **kwargs):
|
||||
from couchpotato.environment import Env
|
||||
callback = getParam('json_callback', None)
|
||||
if callback:
|
||||
return padded_jsonify(callback, *args, **kwargs)
|
||||
else:
|
||||
return jsonify(*args, **kwargs)
|
||||
@@ -0,0 +1,33 @@
|
||||
from couchpotato.core.logger import CPLog
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
class RSS():
|
||||
|
||||
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 []
|
||||
@@ -1,6 +1,5 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.environment import Env
|
||||
from flask.helpers import jsonify
|
||||
from couchpotato.core.helpers.request import getParam, jsonified
|
||||
import os
|
||||
import string
|
||||
|
||||
@@ -45,12 +44,12 @@ class FileBrowser():
|
||||
def view(self):
|
||||
|
||||
try:
|
||||
fb = FileBrowser(Env.getParam('path', '/'))
|
||||
fb = FileBrowser(getParam('path', '/'))
|
||||
dirs = fb.getDirectories()
|
||||
except:
|
||||
dirs = []
|
||||
|
||||
return jsonify({
|
||||
return jsonified({
|
||||
'empty': len(dirs) == 0,
|
||||
'dirs': dirs,
|
||||
})
|
||||
@@ -1,30 +1,36 @@
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import getEvent, fireEvent
|
||||
from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.request import getParams, jsonified
|
||||
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)
|
||||
addApiView('movie.add.select', self.select)
|
||||
|
||||
def search(self):
|
||||
|
||||
a = Env.getParams()
|
||||
a = getParams()
|
||||
|
||||
print fireEvent('provider.movie.search', q = a.get('q'))
|
||||
results = fireEvent('provider.movie.search', q = a.get('q'))
|
||||
|
||||
movies = [
|
||||
{'id': 1, 'name': 'test'}
|
||||
]
|
||||
# Combine movie results
|
||||
movies = []
|
||||
for r in results:
|
||||
movies += r
|
||||
|
||||
return jsonify({
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'empty': len(movies) == 0,
|
||||
'movies': movies,
|
||||
})
|
||||
|
||||
|
||||
def select(self):
|
||||
pass
|
||||
|
||||
a = getParams()
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'added': True,
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
from couchpotato.core.plugins.movie_list.main import MovieList
|
||||
|
||||
def start():
|
||||
return MovieList()
|
||||
|
||||
config = []
|
||||
@@ -0,0 +1,41 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.helpers.request import getParams, jsonified
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Movie, Release
|
||||
|
||||
class MovieList(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
addApiView('movie.list', self.list)
|
||||
|
||||
def list(self):
|
||||
|
||||
a = getParams()
|
||||
|
||||
results = get_session().query(Movie).filter(
|
||||
Movie.releases.any(
|
||||
Release.status.has(identifier = 'wanted')
|
||||
)
|
||||
).all()
|
||||
|
||||
movies = []
|
||||
for movie in results:
|
||||
temp = {
|
||||
'id': movie.id,
|
||||
'name': movie.id,
|
||||
'releases': [],
|
||||
}
|
||||
for release in movie.releases:
|
||||
temp['releases'].append({
|
||||
'status': release.status.label,
|
||||
'quality': release.quality.label
|
||||
})
|
||||
|
||||
movies.append(temp)
|
||||
|
||||
return jsonified({
|
||||
'success': True,
|
||||
'empty': len(movies) == 0,
|
||||
'movies': movies,
|
||||
})
|
||||
@@ -1,9 +1,4 @@
|
||||
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__)
|
||||
|
||||
@@ -15,43 +10,3 @@ class Provider():
|
||||
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 []
|
||||
@@ -1,10 +1,12 @@
|
||||
from __future__ import with_statement
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||
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
|
||||
import simplejson as json
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -21,7 +23,7 @@ class TMDB(Provider):
|
||||
def conf(self, attr):
|
||||
return Env.setting(attr, 'TheMovieDB')
|
||||
|
||||
def search(self, q, limit = 8, alternative = True):
|
||||
def search(self, q, limit = 12, alternative = True):
|
||||
''' Find movie by name '''
|
||||
|
||||
if self.isDisabled():
|
||||
@@ -29,58 +31,61 @@ class TMDB(Provider):
|
||||
|
||||
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)))
|
||||
url = "%s/%s/%s/json/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(simplifyString(q)))
|
||||
|
||||
log.info('Searching: %s' % url)
|
||||
|
||||
data = urllib2.urlopen(url)
|
||||
jsn = json.load(data)
|
||||
|
||||
return self.parseXML(data, limit, alternative = alternative)
|
||||
return self.parse(jsn, limit, alternative = alternative)
|
||||
|
||||
def parseXML(self, data, limit, alternative = True):
|
||||
def parse(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"))
|
||||
for movie in data:
|
||||
|
||||
name = self.gettextelement(movie, "name")
|
||||
imdb = self.gettextelement(movie, "imdb_id")
|
||||
year = str(self.gettextelement(movie, "released"))[:4]
|
||||
year = movie['released'][:4]
|
||||
|
||||
# Poster url
|
||||
poster = ''
|
||||
for p in movie['posters']:
|
||||
p = p['image']
|
||||
if(p['size'] == 'thumb'):
|
||||
poster = p['url']
|
||||
break
|
||||
|
||||
# 1900 is the same as None
|
||||
if year == '1900':
|
||||
year = 'None'
|
||||
year = None
|
||||
|
||||
results.append({
|
||||
'id': id,
|
||||
'name': self.toSaveString(name),
|
||||
'imdb': imdb,
|
||||
'year': year
|
||||
})
|
||||
movie_data = {
|
||||
'id': int(movie['id']),
|
||||
'name': toUnicode(movie['name']),
|
||||
'poster': poster,
|
||||
'imdb': movie['imdb_id'],
|
||||
'year': year,
|
||||
'tagline': 'This is the tagline of the movie',
|
||||
}
|
||||
results.append(movie_data)
|
||||
|
||||
alternativeName = self.gettextelement(movie, "alternative_name")
|
||||
alternativeName = 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
|
||||
})
|
||||
if alternativeName.lower() != movie['name'].lower() and alternativeName.lower() != 'none' and alternativeName != None:
|
||||
movie_data['name'] = toUnicode(alternativeName)
|
||||
results.append(movie_data)
|
||||
|
||||
nr += 1
|
||||
if nr == limit:
|
||||
break
|
||||
|
||||
#log.info('TheMovieDB - Found: %s' % results)
|
||||
log.info('TheMovieDB - Found: %s' % [result['name'] + u' (' + str(result['year']) + ')' for result in results])
|
||||
return results
|
||||
except SyntaxError:
|
||||
log.error('TheMovieDB - Failed to parse XML response from TheMovieDb')
|
||||
except SyntaxError, e:
|
||||
log.error('TheMovieDB - Failed to parse XML response from TheMovieDb: %s' % e)
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import with_statement
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent
|
||||
from flask.helpers import jsonify
|
||||
from couchpotato.core.helpers.request import getParams, jsonified
|
||||
import ConfigParser
|
||||
import os.path
|
||||
import time
|
||||
@@ -107,14 +107,14 @@ class Settings():
|
||||
|
||||
def view(self):
|
||||
|
||||
return jsonify({
|
||||
return jsonified({
|
||||
'options': self.getOptions(),
|
||||
'values': self.getValues()
|
||||
})
|
||||
|
||||
def saveView(self):
|
||||
|
||||
a = Env.getParams()
|
||||
a = getParams()
|
||||
|
||||
section = a.get('section')
|
||||
option = a.get('name')
|
||||
@@ -123,6 +123,6 @@ class Settings():
|
||||
self.set(option, section, value)
|
||||
self.save()
|
||||
|
||||
return jsonify({
|
||||
return jsonified({
|
||||
'success': True,
|
||||
})
|
||||
@@ -2,7 +2,7 @@ from elixir.entity import Entity
|
||||
from elixir.fields import Field
|
||||
from elixir.options import options_defaults
|
||||
from elixir.relationships import OneToMany, ManyToOne
|
||||
from sqlalchemy.types import Integer, String, Unicode
|
||||
from sqlalchemy.types import Integer, String, Unicode, UnicodeText, Boolean
|
||||
|
||||
options_defaults["shortnames"] = True
|
||||
|
||||
@@ -13,43 +13,111 @@ options_defaults["shortnames"] = True
|
||||
# http://elixir.ematia.de/trac/wiki/Recipes/MultipleDatabasesOneMetadata
|
||||
__session__ = None
|
||||
|
||||
class Resource(Entity):
|
||||
"""Represents a resource of movies.
|
||||
This resources can be online or offline."""
|
||||
name = Field(Unicode(255))
|
||||
path = Field(Unicode(255))
|
||||
|
||||
class Movie(Entity):
|
||||
"""Movie Resource a movie could have multiple releases
|
||||
The files belonging to the movie object are global for the whole movie
|
||||
such as trailers, nfo, thumbnails"""
|
||||
|
||||
mooli_id = Field(Integer)
|
||||
|
||||
profile = ManyToOne('Profile')
|
||||
releases = OneToMany('Release')
|
||||
files = OneToMany('File')
|
||||
|
||||
|
||||
class Release(Entity):
|
||||
"""Logically groups all files that belong to a certain release, such as
|
||||
parts of a movie, subtitles, nfo, trailers etc."""
|
||||
files = OneToMany('File')
|
||||
mooli_id = Field(Integer)
|
||||
resource = ManyToOne('Resource')
|
||||
parts of a movie, subtitles."""
|
||||
|
||||
movie = ManyToOne('Movie')
|
||||
status = ManyToOne('Status')
|
||||
quality = ManyToOne('Quality')
|
||||
files = OneToMany('File')
|
||||
history = OneToMany('History')
|
||||
|
||||
|
||||
class Status(Entity):
|
||||
"""The status of a release, such as Downloaded, Deleted, Wanted etc"""
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
label = Field(String(20))
|
||||
|
||||
releases = OneToMany('Release')
|
||||
|
||||
|
||||
class Quality(Entity):
|
||||
"""Quality name of a release, DVD, 720P, DVD-Rip etc"""
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
label = Field(String(20))
|
||||
|
||||
releases = OneToMany('Release')
|
||||
profile_types = ManyToOne('ProfileType')
|
||||
|
||||
class Profile(Entity):
|
||||
""""""
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
label = Field(Unicode(50))
|
||||
order = Field(Integer)
|
||||
wait_for = Field(Integer)
|
||||
|
||||
movie = OneToMany('Movie')
|
||||
profile_type = OneToMany('ProfileType')
|
||||
|
||||
class ProfileType(Entity):
|
||||
""""""
|
||||
|
||||
order = Field(Integer)
|
||||
mark_completed = Field(Boolean)
|
||||
wait_for = Field(Integer)
|
||||
|
||||
type = OneToMany('Quality')
|
||||
profile = ManyToOne('Profile')
|
||||
|
||||
class File(Entity):
|
||||
"""File that belongs to a release."""
|
||||
history = OneToMany('RenameHistory')
|
||||
|
||||
path = Field(Unicode(255), nullable = False, unique = True)
|
||||
# Subtitles can have multiple parts, too
|
||||
part = Field(Integer)
|
||||
|
||||
history = OneToMany('RenameHistory')
|
||||
movie = ManyToOne('Movie')
|
||||
release = ManyToOne('Release')
|
||||
# Let's remember the size so we know about offline media.
|
||||
size = Field(Integer, nullable = False)
|
||||
type = ManyToOne('FileType')
|
||||
properties = OneToMany('FileProperty')
|
||||
|
||||
|
||||
class FileType(Entity):
|
||||
"""Types could be trailer, subtitle, movie, partial movie etc."""
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
name = Field(Unicode(255), nullable = False)
|
||||
name = Field(Unicode(50), nullable = False)
|
||||
|
||||
files = OneToMany('File')
|
||||
|
||||
|
||||
class FileProperty(Entity):
|
||||
"""Properties that can be bound to a file for off-line usage"""
|
||||
|
||||
identifier = Field(String(20))
|
||||
value = Field(Unicode(255), nullable = False)
|
||||
|
||||
file = ManyToOne('File')
|
||||
|
||||
|
||||
class History(Entity):
|
||||
"""History of actions that are connected to a certain release,
|
||||
such as, renamed to, downloaded, deleted, download subtitles etc"""
|
||||
|
||||
message = Field(UnicodeText())
|
||||
release = ManyToOne('Release')
|
||||
|
||||
class RenameHistory(Entity):
|
||||
"""Remembers from where to where files have been moved."""
|
||||
file = ManyToOne('File')
|
||||
|
||||
old = Field(String(255))
|
||||
new = Field(String(255))
|
||||
|
||||
file = ManyToOne('File')
|
||||
Reference in new issue
Block a user