Movie search

Profile Settings
This commit is contained in:
Ruud
2011-03-20 22:12:33 +01:00
parent cc260d3a07
commit 288aa8c0a1
34 changed files with 1408 additions and 178 deletions
+3 -1
View File
@@ -3,6 +3,7 @@ from couchpotato import web
from couchpotato.api import api from couchpotato.api import api
from libs.daemon import createDaemon from libs.daemon import createDaemon
from logging import handlers from logging import handlers
from werkzeug.contrib.cache import FileSystemCache
import logging import logging
import os.path import os.path
import sys import sys
@@ -48,6 +49,7 @@ def cmd_couchpotato(base_path, args):
Env.set('data_dir', options.data_dir) Env.set('data_dir', options.data_dir)
Env.set('db_path', 'sqlite:///' + os.path.join(options.data_dir, 'couchpotato.db')) Env.set('db_path', 'sqlite:///' + os.path.join(options.data_dir, 'couchpotato.db'))
Env.set('cache_dir', os.path.join(options.data_dir, 'cache')) Env.set('cache_dir', os.path.join(options.data_dir, 'cache'))
Env.set('cache', FileSystemCache(Env.get('cache_dir')))
Env.set('quiet', options.quiet) Env.set('quiet', options.quiet)
Env.set('daemonize', options.daemonize) Env.set('daemonize', options.daemonize)
Env.set('args', args) Env.set('args', args)
@@ -133,7 +135,7 @@ def cmd_couchpotato(base_path, args):
# Register modules # Register modules
app.register_module(web, url_prefix = '%s/' % url_base) app.register_module(web, url_prefix = '%s/' % url_base)
app.register_module(api, url_prefix = '%s/%s/%s/' % (url_base, 'api', api_key if not debug else 'apikey')) app.register_module(api, url_prefix = '%s/%s/' % (url_base, api_key if not debug else 'api'))
# Go go go! # Go go go!
app.run(use_reloader = reloader) app.run(use_reloader = reloader)
+8
View File
@@ -24,3 +24,11 @@ def toUnicode(original, *args):
except UnicodeDecodeError: except UnicodeDecodeError:
ascii_text = str(original).encode('string_escape') ascii_text = str(original).encode('string_escape')
return unicode(ascii_text) return unicode(ascii_text)
def is_int(value):
try:
int(value)
return True
except ValueError:
return False
+56 -5
View File
@@ -1,17 +1,67 @@
from flask.globals import current_app from flask.globals import current_app
from flask.helpers import json, jsonify from flask.helpers import json
from libs.werkzeug.urls import url_decode
import flask import flask
import re
def getParams(): def getParams():
return getattr(flask.request, 'args')
params = url_decode(getattr(flask.request, 'environ').get('QUERY_STRING', ''))
reg = re.compile('^[a-z0-9_\.]+$')
current = temp = {}
for param, value in sorted(params.iteritems()):
nest = re.split("([\[\]]+)", param)
if len(nest) > 1:
nested = []
for key in nest:
if reg.match(key):
nested.append(key)
current = temp
for item in nested:
if item is nested[-1]:
current[item] = value
else:
try:
current[item]
except:
current[item] = {}
current = current[item]
else:
temp[param] = value
return dictToList(temp)
def dictToList(params):
if type(params) is dict:
new = {}
for x, value in params.iteritems():
try:
new_value = [dictToList(value2) for value2 in value.itervalues()]
except:
new_value = value
new[x] = new_value
else:
new = params
return new
def getParam(attr, default = None): def getParam(attr, default = None):
return getattr(flask.request, 'args').get(attr, default) return getattr(flask.request, 'args').get(attr, default)
def padded_jsonify(callback, *args, **kwargs): def padded_jsonify(callback, *args, **kwargs):
content = str(callback) + '(' + json.dumps(dict(*args, **kwargs)) + ')' content = str(callback) + '(' + json.dumps(dict(*args, **kwargs)) + ')'
return current_app.response_class(content, mimetype = 'text/javascript') return getattr(current_app, 'response_class')(content, mimetype = 'text/javascript')
def jsonify(mimetype, *args, **kwargs):
content = json.dumps(dict(*args, **kwargs))
return getattr(current_app, 'response_class')(content, mimetype = mimetype)
def jsonified(*args, **kwargs): def jsonified(*args, **kwargs):
from couchpotato.environment import Env from couchpotato.environment import Env
@@ -19,4 +69,5 @@ def jsonified(*args, **kwargs):
if callback: if callback:
return padded_jsonify(callback, *args, **kwargs) return padded_jsonify(callback, *args, **kwargs)
else: else:
return jsonify(*args, **kwargs) return jsonify('text/javascript' if Env.doDebug() else 'application/json', *args, **kwargs)
@@ -0,0 +1,6 @@
from couchpotato.core.plugins.library.main import LibraryPlugin
def start():
return LibraryPlugin()
config = []
+32
View File
@@ -0,0 +1,32 @@
from couchpotato import get_session
from couchpotato.core.event import addEvent
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Library
class LibraryPlugin(Plugin):
def __init__(self):
addEvent('library.add', self.add)
def add(self, attrs = {}):
db = get_session();
l = db.query(Library).filter_by(identifier = attrs.get('identifier')).first()
if not l:
l = Library(
name = attrs.get('name'),
year = attrs.get('year'),
identifier = attrs.get('identifier'),
description = attrs.get('description')
)
db.add(l)
db.commit()
return l
def update(self, item):
pass
@@ -0,0 +1,6 @@
from couchpotato.core.plugins.movie.main import MoviePlugin
def start():
return MoviePlugin()
config = []
+94
View File
@@ -0,0 +1,94 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.request import getParams, jsonified
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Movie, Release, Profile
from couchpotato.environment import Env
from urllib import urlencode
class MoviePlugin(Plugin):
def __init__(self):
addApiView('movie.search', self.search)
addApiView('movie.list', self.list)
addApiView('movie.add', self.add)
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,
})
def search(self):
a = getParams()
cache_key = '%s/%s' % (__name__, urlencode(a))
movies = Env.get('cache').get(cache_key)
if not movies:
results = fireEvent('provider.movie.search', q = a.get('q'))
# Combine movie results
movies = []
for r in results:
movies += r
Env.get('cache').set(cache_key, movies, timeout = 10)
return jsonified({
'success': True,
'empty': len(movies) == 0,
'movies': movies,
})
def add(self):
a = getParams()
db = get_session();
library = fireEvent('library.add', attrs = a)
profile = db.query(Profile).filter_by(identifier = a.get('profile_identifier'))
m = db.query(Movie).filter_by(library = library).first()
if not m:
m = Movie(
library = library,
profile = profile,
)
db.add(m)
db.commit()
return jsonified({
'success': True,
'added': True,
'params': a,
})
@@ -1,6 +0,0 @@
from couchpotato.core.plugins.movie_add.main import MovieAdd
def start():
return MovieAdd()
config = []
@@ -1,36 +0,0 @@
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.request import getParams, jsonified
from couchpotato.core.plugins.base import Plugin
class MovieAdd(Plugin):
def __init__(self):
addApiView('movie.add.search', self.search)
addApiView('movie.add.select', self.select)
def search(self):
a = getParams()
results = fireEvent('provider.movie.search', q = a.get('q'))
# Combine movie results
movies = []
for r in results:
movies += r
return jsonified({
'success': True,
'empty': len(movies) == 0,
'movies': movies,
})
def select(self):
a = getParams()
return jsonified({
'success': True,
'added': True,
})
@@ -1,6 +0,0 @@
from couchpotato.core.plugins.movie_list.main import MovieList
def start():
return MovieList()
config = []
@@ -1,41 +0,0 @@
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,
})
@@ -0,0 +1,6 @@
from couchpotato.core.plugins.profile.main import ProfilePlugin
def start():
return ProfilePlugin()
config = []
+28
View File
@@ -0,0 +1,28 @@
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.request import jsonified, getParams
from couchpotato.core.plugins.base import Plugin
class ProfilePlugin(Plugin):
def __init__(self):
addEvent('profile.get', self.get)
addApiView('profile.save', self.save)
addApiView('profile.delete', self.delete)
def get(self, key = ''):
pass
def save(self):
a = getParams()
return jsonified({
'success': True,
'a': a
})
def delete(self):
pass
+2 -2
View File
@@ -1,7 +1,7 @@
from couchpotato.core.providers.tmdb.main import TMDB from couchpotato.core.providers.tmdb.main import TMDBWrapper
def start(): def start():
return TMDB() return TMDBWrapper()
config = [{ config = [{
'name': 'themoviedb', 'name': 'themoviedb',
+28 -18
View File
@@ -4,6 +4,7 @@ from couchpotato.core.helpers.encoding import simplifyString, toUnicode
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.providers.base import Provider from couchpotato.core.providers.base import Provider
from couchpotato.environment import Env from couchpotato.environment import Env
from libs.themoviedb import tmdb
from urllib import quote_plus from urllib import quote_plus
import copy import copy
import simplejson as json import simplejson as json
@@ -11,7 +12,7 @@ import urllib2
log = CPLog(__name__) log = CPLog(__name__)
class TMDB(Provider): class TMDBWrapper(Provider):
"""Api for theMovieDb""" """Api for theMovieDb"""
type = 'movie' type = 'movie'
@@ -20,6 +21,10 @@ class TMDB(Provider):
def __init__(self): def __init__(self):
addEvent('provider.movie.search', self.search) addEvent('provider.movie.search', self.search)
addEvent('provider.movie.info', self.getInfo)
# Use base wrapper
tmdb.Config.api_key = self.conf('api_key')
def conf(self, attr): def conf(self, attr):
return Env.setting(attr, 'themoviedb') return Env.setting(attr, 'themoviedb')
@@ -32,31 +37,32 @@ class TMDB(Provider):
log.debug('TheMovieDB - Searching for movie: %s' % q) log.debug('TheMovieDB - Searching for movie: %s' % q)
url = "%s/%s/%s/json/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(simplifyString(q))) raw = tmdb.search(simplifyString(q))
log.info('Searching: %s' % url) #url = "%s/%s/%s/json/%s/%s" % (self.apiUrl, 'Movie.search', 'en', self.conf('api_key'), quote_plus(simplifyString(q)))
data = urllib2.urlopen(url)
jsn = json.load(data)
return self.parse(jsn, limit, alternative = alternative) # data = urllib2.urlopen(url)
# jsn = json.load(data)
def parse(self, data, limit, alternative = True): if raw:
if data:
log.debug('TheMovieDB - Parsing RSS') log.debug('TheMovieDB - Parsing RSS')
try: try:
results = [] results = []
nr = 0 nr = 0
for movie in data: for movie in raw:
year = str(movie['released'])[:4] for k, x in movie.iteritems():
print k
print x
year = str(movie.get('released', 'none'))[:4]
# Poster url # Poster url
poster = '' poster = ''
for p in movie['posters']: for p in movie.get('images'):
p = p['image'] if(p.get('type') == 'poster'):
if(p['size'] == 'thumb'): poster = p.get('thumb')
poster = p['url']
break break
# 1900 is the same as None # 1900 is the same as None
@@ -64,16 +70,16 @@ class TMDB(Provider):
year = None year = None
movie_data = { movie_data = {
'id': int(movie['id']), 'id': int(movie.get('id', 0)),
'name': toUnicode(movie['name']), 'name': toUnicode(movie.get('name')),
'poster': poster, 'poster': poster,
'imdb': movie['imdb_id'], 'imdb': movie.get('imdb_id'),
'year': year, 'year': year,
'tagline': 'This is the tagline of the movie', 'tagline': 'This is the tagline of the movie',
} }
results.append(copy.deepcopy(movie_data)) results.append(copy.deepcopy(movie_data))
alternativeName = movie['alternative_name'] alternativeName = movie.get('alternative_name')
if alternativeName and alternative: if alternativeName and alternative:
if alternativeName.lower() != movie['name'].lower() and alternativeName.lower() != 'none' and alternativeName != None: if alternativeName.lower() != movie['name'].lower() and alternativeName.lower() != 'none' and alternativeName != None:
movie_data['name'] = toUnicode(alternativeName) movie_data['name'] = toUnicode(alternativeName)
@@ -88,6 +94,10 @@ class TMDB(Provider):
log.error('TheMovieDB - Failed to parse XML response from TheMovieDb: %s' % e) log.error('TheMovieDB - Failed to parse XML response from TheMovieDb: %s' % e)
return False return False
return results
def getInfo(self):
pass
def isDisabled(self): def isDisabled(self):
if self.conf('api_key') == '': if self.conf('api_key') == '':
+2 -8
View File
@@ -1,6 +1,7 @@
from __future__ import with_statement from __future__ import with_statement
from couchpotato.api import addApiView from couchpotato.api import addApiView
from couchpotato.core.event import addEvent from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import is_int
from couchpotato.core.helpers.request import getParams, jsonified from couchpotato.core.helpers.request import getParams, jsonified
import ConfigParser import ConfigParser
import os.path import os.path
@@ -60,7 +61,7 @@ class Settings():
return default return default
def cleanValue(self, value): def cleanValue(self, value):
if(self.is_int(value)): if(is_int(value)):
return int(value) return int(value)
if str(value).lower() in self.bool: if str(value).lower() in self.bool:
@@ -91,13 +92,6 @@ class Settings():
if not self.p.has_option(section, option): if not self.p.has_option(section, option):
self.p.set(section, option, value) self.p.set(section, option, value)
def is_int(self, value):
try:
int(value)
return True
except ValueError:
return False
def addOptions(self, section_name, options): def addOptions(self, section_name, options):
self.options[section_name] = options self.options[section_name] = options
+20 -2
View File
@@ -2,7 +2,8 @@ from elixir.entity import Entity
from elixir.fields import Field from elixir.fields import Field
from elixir.options import options_defaults from elixir.options import options_defaults
from elixir.relationships import OneToMany, ManyToOne from elixir.relationships import OneToMany, ManyToOne
from sqlalchemy.types import Integer, String, Unicode, UnicodeText, Boolean from sqlalchemy.types import Integer, String, Unicode, UnicodeText, Boolean, \
Float
options_defaults["shortnames"] = True options_defaults["shortnames"] = True
@@ -19,13 +20,26 @@ class Movie(Entity):
The files belonging to the movie object are global for the whole movie The files belonging to the movie object are global for the whole movie
such as trailers, nfo, thumbnails""" such as trailers, nfo, thumbnails"""
mooli_id = Field(Integer) library = ManyToOne('Library')
profile = ManyToOne('Profile') profile = ManyToOne('Profile')
releases = OneToMany('Release') releases = OneToMany('Release')
files = OneToMany('File') files = OneToMany('File')
class Library(Entity):
title = Field(Unicode)
year = Field(Integer)
identifier = Field(Unicode)
rating = Field(Float)
plot = Field(UnicodeText)
tagline = Field(UnicodeText(255))
movie = OneToMany('Movie')
class Release(Entity): class Release(Entity):
"""Logically groups all files that belong to a certain release, such as """Logically groups all files that belong to a certain release, such as
parts of a movie, subtitles.""" parts of a movie, subtitles."""
@@ -55,6 +69,7 @@ class Quality(Entity):
releases = OneToMany('Release') releases = OneToMany('Release')
profile_types = ManyToOne('ProfileType') profile_types = ManyToOne('ProfileType')
class Profile(Entity): class Profile(Entity):
"""""" """"""
@@ -66,6 +81,7 @@ class Profile(Entity):
movie = OneToMany('Movie') movie = OneToMany('Movie')
profile_type = OneToMany('ProfileType') profile_type = OneToMany('ProfileType')
class ProfileType(Entity): class ProfileType(Entity):
"""""" """"""
@@ -76,6 +92,7 @@ class ProfileType(Entity):
type = OneToMany('Quality') type = OneToMany('Quality')
profile = ManyToOne('Profile') profile = ManyToOne('Profile')
class File(Entity): class File(Entity):
"""File that belongs to a release.""" """File that belongs to a release."""
@@ -114,6 +131,7 @@ class History(Entity):
message = Field(UnicodeText()) message = Field(UnicodeText())
release = ManyToOne('Release') release = ManyToOne('Release')
class RenameHistory(Entity): class RenameHistory(Entity):
"""Remembers from where to where files have been moved.""" """Remembers from where to where files have been moved."""
+1
View File
@@ -7,6 +7,7 @@ class Env:
_debug = False _debug = False
_settings = Settings() _settings = Settings()
_loader = Loader() _loader = Loader()
_cache = None
_options = None _options = None
_args = None _args = None
_quiet = False _quiet = False
+100 -9
View File
@@ -35,6 +35,9 @@ Block.Search = new Class({
self.OuterClickStack = new EventStack.OuterClick(); self.OuterClickStack = new EventStack.OuterClick();
//debug
//self.input.set('value', 'kick ass')
//self.autocomplete()
}, },
clear: function(e){ clear: function(e){
@@ -98,7 +101,7 @@ Block.Search = new Class({
self.hideResults(false) self.hideResults(false)
if(!cache){ if(!cache){
self.api_request = Api.request('movie.add.search', { self.api_request = Api.request('movie.search', {
'data': { 'data': {
'q': q 'q': q
}, },
@@ -118,7 +121,7 @@ Block.Search = new Class({
self.spinner.hide(); self.spinner.hide();
self.cache[q] = json self.cache[q] = json
self.movies = [] self.movies = {}
self.results.empty() self.results.empty()
Object.each(json.movies, function(movie){ Object.each(json.movies, function(movie){
@@ -126,9 +129,14 @@ Block.Search = new Class({
if(!movie.imdb || (movie.imdb && !self.results.getElement('#'+movie.imdb))){ if(!movie.imdb || (movie.imdb && !self.results.getElement('#'+movie.imdb))){
var m = new Block.Search.Item(movie); var m = new Block.Search.Item(movie);
$(m).inject(self.results) $(m).inject(self.results)
self.movies[movie.imdb || 'r-'+Math.floor(Math.random()*10000)] = m
}
else {
self.movies[movie.imdb].alternativeName({
'name': movie.name
})
} }
self.movies.include(m)
}); });
}, },
@@ -149,6 +157,7 @@ Block.Search.Item = new Class({
var self = this; var self = this;
self.info = info; self.info = info;
self.alternative_names = [];
self.create(); self.create();
@@ -163,11 +172,7 @@ Block.Search.Item = new Class({
self.el = new Element('div.movie', { self.el = new Element('div.movie', {
'id': info.imdb 'id': info.imdb
}).adopt( }).adopt(
new Element('div.add').adopt( self.options = new Element('div.options'),
new Element('span', {
'text': 'test'
})
),
self.data_container = new Element('div.data', { self.data_container = new Element('div.data', {
'tween': { 'tween': {
duration: 400, duration: 400,
@@ -208,11 +213,23 @@ Block.Search.Item = new Class({
}).inject(self.starring) }).inject(self.starring)
}) })
} }
self.alternativeName({
'name': info.name
});
},
alternativeName: function(alternative){
var self = this;
self.alternative_names.include(alternative);
}, },
showOptions: function(){ showOptions: function(){
var self = this; var self = this;
self.createOptions();
if(!self.width) if(!self.width)
self.width = self.data_container.getCoordinates().width self.width = self.data_container.getCoordinates().width
@@ -222,6 +239,80 @@ Block.Search.Item = new Class({
}, },
add: function(e){
var self = this;
(e).stop();
Api.request('movie.add', {
'data': {
'identifier': self.info.imdb,
'name': self.name_select.get('value'),
'quality': self.quality_select.get('value')
},
'useSpinner': true,
'spinnerTarget': self.options,
'onComplete': function(){
self.options.empty();
self.options.adopt(
new Element('div.message', {
'text': 'Movie succesfully added.'
})
);
},
'onFailure': function(){
self.options.empty();
self.options.adopt(
new Element('div.message', {
'text': 'Something went wrong, check the logs for more info.'
})
);
}
});
},
createOptions: function(){
var self = this;
if(!self.options.hasClass('set')){
self.options.adopt(
new Element('div').adopt(
self.info.poster ? new Element('img.thumbnail', {
'src': self.info.poster
}) : null,
self.name_select = new Element('select', {
'name': 'name'
}),
self.quality_select = new Element('select', {
'name': 'profile_identifier'
}),
new Element('a.button', {
'text': 'Add',
'events': {
'click': self.add.bind(self)
}
})
)
);
Array.each(self.alternative_names, function(alt){
new Element('option', {
'text': alt.name
}).inject(self.name_select)
})
Array.each(Quality.profiles, function(q){
new Element('option', {
'value': q.indentifier,
'text': q.label
}).inject(self.quality_select)
});
self.options.addClass('set');
}
},
closeOptions: function(){ closeOptions: function(){
var self = this; var self = this;
@@ -232,4 +323,4 @@ Block.Search.Item = new Class({
return this.el return this.el
} }
}) });
+9 -3
View File
@@ -1,6 +1,6 @@
var CouchPotato = new Class({ var CouchPotato = new Class({
Implements: [Options], Implements: [Events, Options],
defaults: { defaults: {
page: 'wanted', page: 'wanted',
@@ -11,7 +11,7 @@ var CouchPotato = new Class({
pages: [], pages: [],
block: [], block: [],
initialize: function(options) { setup: function(options) {
var self = this; var self = this;
self.setOptions(options); self.setOptions(options);
@@ -62,6 +62,8 @@ var CouchPotato = new Class({
$(pg).inject(self.content); $(pg).inject(self.content);
}); });
self.fireEvent('load')
}, },
openPage: function(url) { openPage: function(url) {
@@ -85,9 +87,13 @@ var CouchPotato = new Class({
getBlock: function(block_name){ getBlock: function(block_name){
return this.block[block_name] return this.block[block_name]
},
getPage: function(name){
return this.pages[name]
} }
}); });
window.App = new CouchPotato();
var ApiClass = new Class({ var ApiClass = new Class({
+54 -32
View File
@@ -105,20 +105,7 @@ Page.Settings = new Class({
// Create tabs // Create tabs
Object.each(self.tabs, function(tab, tab_name){ Object.each(self.tabs, function(tab, tab_name){
if(!self.tabs[tab_name].tab){ self.createTab(tab_name, tab)
var tab_el = new Element('li').adopt(
new Element('a', {
'href': '/'+self.name+'/'+tab_name+'/',
'text': tab.label.capitalize()
})
).inject(self.tabs_container);
self.tabs[tab_name] = Object.merge(self.tabs[tab_name], {
'tab': tab_el,
'content': new Element('div.tab_content').inject(self.containers),
'groups': {}
})
}
}); });
// Add content to tabs // Add content to tabs
@@ -128,17 +115,7 @@ Page.Settings = new Class({
section.groups.sortBy('order').each(function(group){ section.groups.sortBy('order').each(function(group){
// Create the group // Create the group
var group_el = new Element('fieldset', { var group_el = self.createGroup(group).inject(self.tabs[group.tab].content);
'class': group.advanced ? 'inlineLabels advanced' : 'inlineLabels'
}).adopt(
new Element('h2', {
'text': group.label
}).adopt(
new Element('span.hint', {
'text': group.description
})
)
).inject(self.tabs[group.tab].content);
self.tabs[group.tab].groups[group.name] = group_el self.tabs[group.tab].groups[group.name] = group_el
@@ -152,8 +129,57 @@ Page.Settings = new Class({
}); });
}); });
self.fireEvent('create');
self.openTab(); self.openTab();
},
createTab: function(tab_name, tab){
var self = this;
if(self.tabs[tab_name] && self.tabs[tab_name].tab)
return self.tabs[tab_name].tab
var tab_el = new Element('li').adopt(
new Element('a', {
'href': '/'+self.name+'/'+tab_name+'/',
'text': tab.label.capitalize()
})
).inject(self.tabs_container);
if(!self.tabs[tab_name])
self.tabs[tab_name] = {
'label': tab.label
}
self.tabs[tab_name] = Object.merge(self.tabs[tab_name], {
'tab': tab_el,
'content': new Element('div.tab_content').inject(self.containers),
'groups': {}
})
return self.tabs[tab_name]
},
createGroup: function(group){
var self = this;
var group_el = new Element('fieldset', {
'class': group.advanced ? 'inlineLabels advanced' : 'inlineLabels'
}).adopt(
new Element('h2', {
'text': group.label
}).adopt(
new Element('span.hint', {
'text': group.description
})
)
)
return group_el
} }
}); });
@@ -226,7 +252,7 @@ var OptionBase = new Class({
save: function(){ save: function(){
var self = this; var self = this;
self.api().request('setting.save', { Api.request('setting.save', {
'data': { 'data': {
'section': self.section, 'section': self.section,
'name': self.name, 'name': self.name,
@@ -273,10 +299,6 @@ var OptionBase = new Class({
return self.page.getValue(self.section, self.name); return self.page.getValue(self.section, self.name);
}, },
api: function(){
return this.page.api();
},
inject: function(el, position){ inject: function(el, position){
this.el.inject(el, position); this.el.inject(el, position);
return this.el; return this.el;
@@ -448,7 +470,7 @@ Option.Directory = new Class({
self.fillBrowser() self.fillBrowser()
} }
else { else {
self.api().request('directory.list', { Api.request('directory.list', {
'data': { 'data': {
'path': c 'path': c
}, },
@@ -461,7 +483,7 @@ Option.Directory = new Class({
var self = this; var self = this;
var v = self.input.get('value'); var v = self.input.get('value');
var sep = self.api().getOption('path_sep'); var sep = Api.getOption('path_sep');
var dirs = v.split(sep); var dirs = v.split(sep);
dirs.pop(); dirs.pop();
+317
View File
@@ -0,0 +1,317 @@
var QualityBase = new Class({
tab: '',
content: '',
setup: function(data){
var self = this;
self.profiles = data.profiles;
self.qualities = data.qualities;
App.addEvent('load', self.addSettings.bind(self))
},
addSettings: function(){
var self = this;
self.settings = App.getPage('Settings')
self.settings.addEvent('create', function(){
var tab = self.settings.createTab('profile', {
'label': 'Profile',
'name': 'profile'
});
self.tab = tab.tab;
self.content = tab.content;
self.createProfiles();
self.createOrdering();
self.createSizes();
})
},
/**
* Profiles
*/
createProfiles: function(){
var self = this;
self.settings.createGroup({
'label': 'Custom',
'description': 'Discriptions'
}).inject(self.content).adopt(
new Element('a.add_new', {
'text': 'Create a new quality profile',
'events': {
'click': self.createNewProfile.bind(self)
}
}),
self.profile_container = new Element('div.container')
)
Object.each(self.profiles, self.createNewProfile.bind(self))
},
createNewProfile: function(data, nr){
var self = this;
self.profiles[nr] = new Profile(data);
$(self.profiles[nr]).inject(self.profile_container)
},
/**
* Ordering
*/
createOrdering: function(){
var self = this;
self.settings.createGroup({
'label': 'Order',
'description': 'Discriptions'
}).inject(self.content)
},
/**
* Sizes
*/
createSizes: function(){
var self = this;
self.settings.createGroup({
'label': 'Sizes',
'description': 'Discriptions',
'advanced': true
}).inject(self.content)
}
});
window.Quality = new QualityBase();
var Profile = new Class({
data: {},
types: [],
initialize: function(data){
var self = this;
self.data = data;
self.types = [];
self.create();
self.el.addEvents({
'change:relay(select, input[type=checkbox])': self.save.bind(self, 0),
'keyup:relay(input[type=text])': self.save.bind(self, [300])
});
},
create: function(){
var self = this;
var data = self.data;
self.el = new Element('div', {
'class': 'profile'
}).adopt(
new Element('h4', {'text': data.label}),
new Element('span.delete', {
'html': 'del',
'events': {
'click': self.del.bind(self)
}
}),
new Element('div', {
'class': 'ctrlHolder'
}).adopt(
new Element('label', {'text':'Name'}),
new Element('input.label.textInput.large', {
'type':'text',
'value': data.label
})
),
new Element('div.ctrlHolder').adopt(
new Element('label', {'text':'Wait'}),
new Element('input.wait_for.textInput.xsmall', {
'type':'text',
'value': data.wait_for
}),
new Element('span', {'text':' day(s) for better quality.'})
),
new Element('div.ctrlHolder').adopt(
new Element('label', {'text': 'Qualities'}),
self.type_container = new Element('div.types').adopt(
new Element('div.head').adopt(
new Element('span.quality_type', {'text': 'Search for'}),
new Element('span.finish', {'html': '<acronym title="Won\'t download anything else if it has found this quality.">Finish</acronym>'})
)
),
new Element('a.addType', {
'text': 'Add another quality to search for.',
'href': '#',
'events': {
'click': self.addType.bind(self)
}
})
)
);
if(data.types)
Object.each(data.types, self.addType.bind(self))
},
save: function(delay){
var self = this;
if(self.save_timer) clearTimeout(self.save_timer);
self.save_timer = (function(){
Api.request('profile.save', {
'data': self.getData(),
'useSpinner': true,
'spinnerOptions': {
'target': self.el
}
});
}).delay(delay, self)
},
getData: function(){
var self = this;
var data = {
'id' : self.data.id,
'label' : self.el.getElement('.label').get('value'),
'wait_for' : self.el.getElement('.wait_for').get('value'),
'types': []
}
Object.each(self.types, function(type){
if(!type.deleted)
data.types.include(type.getData());
})
return data
},
addType: function(data){
var self = this;
var t = new Profile.Type(data);
$(t).inject(self.type_container);
self.types.include(t);
},
del: function(){
var self = this;
Api.request('profile.delete', {
'data': {
'id': self.data.id
},
'useSpinner': true,
'spinnerOptions': {
'target': self.el
},
'onComplete': function(){
self.el.destroy();
}
});
},
toElement: function(){
return this.el
}
});
Profile.Type = Class({
deleted: false,
initialize: function(data){
var self = this;
self.data = data;
self.create();
},
create: function(){
var self = this;
var data = self.data;
self.el = new Element('div.type').adopt(
new Element('span.quality_type').adopt(
self.fillQualities()
),
new Element('span.finish').adopt(
self.finish = new Element('input', {
'type':'checkbox',
'class':'finish',
'checked': data.finish
})
),
new Element('span.delete', {
'html': 'del',
'events': {
'click': self.del.bind(self)
}
}),
new Element('span', {
'class':'handle'
})
)
},
fillQualities: function(){
var self = this;
self.qualities = new Element('select');
Object.each(Quality.qualities, function(q){
new Element('option', {
'text': q.label,
'value': q.identifier
}).inject(self.qualities)
});
self.qualities.set('value', self.data.quality);
return self.qualities;
},
getData: function(){
var self = this;
return {
'quality': self.qualities.get('value'),
'finish': +self.finish.checked
}
},
del: function(){
var self = this;
self.el.hide();
self.deleted = true;
},
toElement: function(){
return this.el;
}
})
+20
View File
@@ -101,6 +101,26 @@ form {
background: #f7f7f7 url('../images/spinner.gif') no-repeat center; background: #f7f7f7 url('../images/spinner.gif') no-repeat center;
} }
.button {
background: #5082bc url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAyCAYAAACd+7GKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAClJREFUeNpi/v//vwMTAwPDfzjBgMpFI/7hFSOT9Y8qRuF3JLoHAQIMAHYtMmRA+CugAAAAAElFTkSuQmCC") repeat-x;
padding: 5px 10px 6px;
color: #fff;
text-decoration: none;
font-weight: bold;
line-height: 1;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-box-shadow: 0 1px 3px rgba(0,0,0,0.5);
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5);
box-shadow: 0 1px 3px rgba(0,0,0,0.5);
text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
border-bottom: 1px solid rgba(0,0,0,0.25);
cursor: pointer;
}
/*** Navigation ***/ /*** Navigation ***/
.header { .header {
background: #f7f7f7; background: #f7f7f7;
+23 -2
View File
@@ -66,9 +66,30 @@
min-height: 140px; min-height: 140px;
} }
.search_form .results .movie .add { .search_form .results .movie .options {
min-height: 140px; height: 140px;
} }
.search_form .results .movie .options > div {
margin: 0 10px;
}
.search_form .results .movie .options .thumbnail {
vertical-align: middle;
}
.search_form .results .movie .options select {
vertical-align: middle;
display: inline-block;
margin-right: 10px;
}
.search_form .results .movie .options select[name=name] { width: 180px; }
.search_form .results .movie .options select[name=quality] { width: 90px; }
.search_form .results .movie .options .button {
vertical-align: middle;
display: inline-block;
}
.search_form .results .movie .data { .search_form .results .movie .data {
padding: 0 15px; padding: 0 15px;
+39 -7
View File
@@ -8,14 +8,14 @@
<link rel="stylesheet" href="{{ url_for('.static', filename='style/page/settings.css') }}"> <link rel="stylesheet" href="{{ url_for('.static', filename='style/page/settings.css') }}">
<link rel="stylesheet" href="{{ url_for('.static', filename='style/movie_add.css') }}"> <link rel="stylesheet" href="{{ url_for('.static', filename='style/movie_add.css') }}">
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/mootools.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/mootools.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/mootools_more.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/mootools_more.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/uniform.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/uniform.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/couchpotato.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/couchpotato.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/history.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/history.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/eventstack.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/eventstack.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/eventstack_outerclick.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/eventstack_outerclick.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/block.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/block.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/block/navigation.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/block/navigation.js') }}"></script>
@@ -29,6 +29,8 @@
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/soon.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/soon.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/manage.js') }}"></script> <script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/manage.js') }}"></script>
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/quality.js') }}"></script>
<link href="{{ url_for('.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" /> <link href="{{ url_for('.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" />
<script type="text/javascript"> <script type="text/javascript">
@@ -39,9 +41,39 @@
'url': '{{ url_for('api.index') }}', 'url': '{{ url_for('api.index') }}',
'path_sep': '{{ sep }}', 'path_sep': '{{ sep }}',
'is_remote': false 'is_remote': false
});
Quality.setup({
'profiles': [
{
'identifier': 'hd',
'label': 'HD',
'wait_for': 7,
'types': [
{'quality': '720p', 'finish': true},
{'quality': '1080p', 'finish': true}
]
},{
'identifier': 'best',
'label': 'Best',
'wait_for': 7,
'types': [
{'quality': '720p', 'finish': true},
{'quality': '1080p', 'finish': true},
{'quality': 'brrip'},
{'quality': 'dvdrip'}
]
}
],
'qualities': [
{'identifier': '1080p', 'label': '1080P'},
{'identifier': '720p', 'label': '720P'},
{'identifier': 'brrip', 'label': 'BR-Rip'},
{'identifier': 'dvdrip', 'label': 'DVD-Rip'}
]
}) })
var cp = new CouchPotato({ App.setup({
'base_url': '{{ request.path }}' 'base_url': '{{ request.path }}'
}); });
}) })
View File
+554
View File
@@ -0,0 +1,554 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
#author:doganaydin /// forked from dbr/Ben
#project:themoviedb
#repository:http://github.com/doganaydin/themoviedb
#license: LGPLv2 http://www.gnu.org/licenses/lgpl.html
"""An interface to the themoviedb.org API"""
__author__ = "doganaydin"
__version__ = "0.2"
class Config:
lang = 'en'
api_key = 'API_KEY'
urls = {
'movie.search': "http://api.themoviedb.org/2.1/Movie.search/%(lang)s/xml/%(apikey)s/%%s",
'movie.getInfo': "http://api.themoviedb.org/2.1/Movie.getInfo/%(lang)s/xml/%(apikey)s/%%s",
'media.getInfo': "http://api.themoviedb.org/2.1/Media.getInfo/%(lang)s/xml/%(apikey)s/%%s/%%s",
'imdb.lookUp': "http://api.themoviedb.org/2.1/Movie.imdbLookup/%(lang)s/xml/%(apikey)s/%%s",
}
@staticmethod
def getUrl(type):
return Config.urls[type] % {'lang': Config.lang, 'apikey': Config.api_key}
import os, struct, urllib, urllib2, xml.etree.cElementTree as ElementTree
class TmdBaseError(Exception):
pass
class TmdNoResults(TmdBaseError):
pass
class TmdHttpError(TmdBaseError):
pass
class TmdXmlError(TmdBaseError):
pass
def opensubtitleHashFile(name):
"""Hashes a file using OpenSubtitle's method.
> In natural language it calculates: size + 64bit chksum of the first and
> last 64k (even if they overlap because the file is smaller than 128k).
A slightly more Pythonic version of the Python solution on..
http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes
"""
longlongformat = 'q'
bytesize = struct.calcsize(longlongformat)
f = open(name, "rb")
filesize = os.path.getsize(name)
fhash = filesize
if filesize < 65536 * 2:
raise ValueError("File size must be larger than %s bytes (is %s)" % (65536 * 2, filesize))
for x in range(65536 / bytesize):
buf = f.read(bytesize)
(l_value,) = struct.unpack(longlongformat, buf)
fhash += l_value
fhash = fhash & 0xFFFFFFFFFFFFFFFF # to remain as 64bit number
f.seek(max(0, filesize - 65536), 0)
for x in range(65536 / bytesize):
buf = f.read(bytesize)
(l_value,) = struct.unpack(longlongformat, buf)
fhash += l_value
fhash = fhash & 0xFFFFFFFFFFFFFFFF
f.close()
return "%016x" % fhash
class XmlHandler:
"""Deals with retrieval of XML files from API"""
def __init__(self, url):
self.url = url
def _grabUrl(self, url):
try:
urlhandle = urllib2.urlopen(url)
except IOError, errormsg:
raise TmdHttpError(errormsg)
if urlhandle.code >= 400:
raise TmdHttpError("HTTP status code was %d" % urlhandle.code)
return urlhandle.read()
def getEt(self):
xml = self._grabUrl(self.url)
try:
et = ElementTree.fromstring(xml)
except SyntaxError, errormsg:
raise TmdXmlError(errormsg)
return et
class SearchResults(list):
"""Stores a list of Movie's that matched the search"""
def __repr__(self):
return "<Search results: %s>" % (list.__repr__(self))
class MovieResult(dict):
"""A dict containing the information about a specific search result"""
def __repr__(self):
return "<MovieResult: %s (%s)>" % (self.get("name"), self.get("released"))
def info(self):
"""Performs a MovieDb.getMovieInfo search on the current id, returns
a Movie object
"""
cur_id = self['id']
info = MovieDb().getMovieInfo(cur_id)
return info
class Movie(dict):
"""A dict containing the information about the film"""
def __repr__(self):
return "<MovieResult: %s (%s)>" % (self.get("name"), self.get("released"))
class Categories(dict):
"""Stores category information"""
def set(self, category_et):
"""Takes an elementtree Element ('category') and stores the url,
using the type and name as the dict key.
For example:
<category type="genre" url="http://themoviedb.org/encyclopedia/category/80" name="Crime"/>
..becomes:
categories['genre']['Crime'] = 'http://themoviedb.org/encyclopedia/category/80'
"""
_type = category_et.get("type")
name = category_et.get("name")
url = category_et.get("url")
self.setdefault(_type, {})[name] = url
self[_type][name] = url
class Studios(dict):
"""Stores category information"""
def set(self, studio_et):
"""Takes an elementtree Element ('studio') and stores the url,
using the name as the dict key.
For example:
<studio url="http://www.themoviedb.org/encyclopedia/company/20" name="Miramax Films"/>
..becomes:
studios['name'] = 'http://www.themoviedb.org/encyclopedia/company/20'
"""
name = studio_et.get("name")
url = studio_et.get("url")
self[name] = url
class Countries(dict):
"""Stores country information"""
def set(self, country_et):
"""Takes an elementtree Element ('country') and stores the url,
using the name and code as the dict key.
For example:
<country url="http://www.themoviedb.org/encyclopedia/country/223" name="United States of America" code="US"/>
..becomes:
countries['code']['name'] = 'http://www.themoviedb.org/encyclopedia/country/223'
"""
code = country_et.get("code")
name = country_et.get("name")
url = country_et.get("url")
self.setdefault(code, {})[name] = url
class Image(dict):
"""Stores image information for a single poster/backdrop (includes
multiple sizes)
"""
def __init__(self, _id, _type, size, url):
self['id'] = _id
self['type'] = _type
def largest(self):
for csize in ["original", "mid", "cover", "thumb"]:
if csize in self:
return csize
def __repr__(self):
return "<Image (%s for ID %s)>" % (self['type'], self['id'])
class ImagesList(list):
"""Stores a list of Images, and functions to filter "only posters" etc"""
def set(self, image_et):
"""Takes an elementtree Element ('image') and stores the url,
along with the type, id and size.
Is a list containing each image as a dictionary (which includes the
various sizes)
For example:
<image type="poster" size="original" url="http://images.themoviedb.org/posters/4181/67926_sin-city-02-color_122_207lo.jpg" id="4181"/>
..becomes:
images[0] = {'id':4181', 'type': 'poster', 'original': 'http://images.themov...'}
"""
_type = image_et.get("type")
_id = image_et.get("id")
size = image_et.get("size")
url = image_et.get("url")
cur = self.find_by('id', _id)
if len(cur) == 0:
nimg = Image(_id = _id, _type = _type, size = size, url = url)
self.append(nimg)
elif len(cur) == 1:
cur[0][size] = url
else:
raise ValueError("Found more than one poster with id %s, this should never happen" % (_id))
def find_by(self, key, value):
ret = []
for cur in self:
if cur[key] == value:
ret.append(cur)
return ret
@property
def posters(self):
return self.find_by('type', 'poster')
@property
def backdrops(self):
return self.find_by('type', 'backdrop')
class CrewRoleList(dict):
"""Stores a list of roles, such as director, actor etc
>>> import tmdb
>>> tmdb.getMovieInfo(550)['cast'].keys()[:5]
['casting', 'producer', 'author', 'sound editor', 'actor']
"""
pass
class CrewList(list):
"""Stores list of crew in specific role
>>> import tmdb
>>> tmdb.getMovieInfo(550)['cast']['author']
[<author (id 7468): Chuck Palahniuk>, <author (id 7469): Jim Uhls>]
"""
pass
class Person(dict):
"""Stores information about a specific member of cast"""
def __init__(self, job, _id, name, character, url):
self['job'] = job
self['id'] = _id
self['name'] = name
self['character'] = character
self['url'] = url
def __repr__(self):
if self['character'] is None or self['character'] == "":
return "<%(job)s (id %(id)s): %(name)s>" % self
else:
return "<%(job)s (id %(id)s): %(name)s (as %(character)s)>" % self
class MovieDb:
"""Main interface to www.themoviedb.com
The search() method searches for the film by title.
The getMovieInfo() method retrieves information about a specific movie using themoviedb id.
"""
def _parseSearchResults(self, movie_element):
cur_movie = MovieResult()
cur_images = ImagesList()
for item in movie_element.getchildren():
if item.tag.lower() == "images":
for subitem in item.getchildren():
cur_images.set(subitem)
else:
cur_movie[item.tag] = item.text
cur_movie['images'] = cur_images
return cur_movie
def _parseMovie(self, movie_element):
cur_movie = Movie()
cur_categories = Categories()
cur_studios = Studios()
cur_countries = Countries()
cur_images = ImagesList()
cur_cast = CrewRoleList()
for item in movie_element.getchildren():
if item.tag.lower() == "categories":
for subitem in item.getchildren():
cur_categories.set(subitem)
elif item.tag.lower() == "studios":
for subitem in item.getchildren():
cur_studios.set(subitem)
elif item.tag.lower() == "countries":
for subitem in item.getchildren():
cur_countries.set(subitem)
elif item.tag.lower() == "images":
for subitem in item.getchildren():
cur_images.set(subitem)
elif item.tag.lower() == "cast":
for subitem in item.getchildren():
job = subitem.get("job").lower()
p = Person(
job = job,
_id = subitem.get("id"),
name = subitem.get("name"),
character = subitem.get("character"),
url = subitem.get("url"),
)
cur_cast.setdefault(job, CrewList()).append(p)
else:
cur_movie[item.tag] = item.text
cur_movie['categories'] = cur_categories
cur_movie['studios'] = cur_studios
cur_movie['countries'] = cur_countries
cur_movie['images'] = cur_images
cur_movie['cast'] = cur_cast
return cur_movie
def search(self, title):
"""Searches for a film by its title.
Returns SearchResults (a list) containing all matches (Movie instances)
"""
title = urllib.quote(title.encode("utf-8"))
url = Config.getUrl('movie.search') % (title)
etree = XmlHandler(url).getEt()
search_results = SearchResults()
for cur_result in etree.find("movies").findall("movie"):
cur_movie = self._parseSearchResults(cur_result)
search_results.append(cur_movie)
return search_results
def getMovieInfo(self, id):
"""Returns movie info by it's TheMovieDb ID.
Returns a Movie instance
"""
url = Config.getUrl('movie.getInfo') % (id)
etree = XmlHandler(url).getEt()
moviesTree = etree.find("movies").findall("movie")
if len(moviesTree) == 0:
raise TmdNoResults("No results for id %s" % id)
return self._parseMovie(moviesTree[0])
def mediaGetInfo(self, hash, size):
"""Used to retrieve specific information about a movie but instead of
passing a TMDb ID, you pass a file hash and filesize in bytes
"""
url = Config.getUrl('media.getInfo') % (hash, size)
etree = XmlHandler(url).getEt()
moviesTree = etree.find("movies").findall("movie")
if len(moviesTree) == 0:
raise TmdNoResults("No results for hash %s" % hash)
return [self._parseMovie(x) for x in moviesTree]
def imdbLookup(self, id = 0, title = False):
if id > 0:
url = Config.getUrl('imdb.lookUp') % (id)
else:
_imdb_id = self.search(title)[0]["imdb_id"]
url = Config.getUrl('imdb.lookUp') % (_imdb_id)
etree = XmlHandler(url).getEt()
lookup_results = SearchResults()
for cur_lookup in etree.find("movies").findall("movie"):
cur_movie = self._parseSearchResults(cur_lookup)
lookup_results.append(cur_movie)
return lookup_results
# Shortcuts for tmdb search method
# using:
# movie = tmdb.tmdb("Sin City")
# print movie.getRating -> 7.0
class tmdb:
def __init__(self, name):
"""Convenience wrapper for MovieDb.search - so you can do..
>>> import tmdb
>>> movie = tmdb.tmdb("Fight Club")
>>> ranking = movie.getRanking() or votes = movie.getVotes()
<Search results: [<MovieResult: Fight Club (1999-09-16)>]>
"""
mdb = MovieDb()
self.movie = mdb.search(name)[int(result)]
def getRating(self, i):
return self.movie[i]["rating"]
def getVotes(self, i):
return self.movie[i]["votes"]
def getName(self, i):
return self.movie[i]["name"]
def getLanguage(self, i):
return self.movie[i]["language"]
def getCertification(self, i):
return self.movie[i]["certification"]
def getUrl(self, i):
return self.movie[i]["url"]
def getOverview(self, i):
return self.movie[i]["overview"]
def getPopularity(self, i):
return self.movie[i]["popularity"]
def getOriginalName(self, i):
return self.movie[i]["original_name"]
def getLastModified(self, i):
return self.movie[i]["last_modified_at"]
def getImdbId(self, i):
return self.movie[i]["imdb_id"]
def getReleased(self, i):
return self.movie[i]["released"]
def getScore(self, i):
return self.movie[i]["score"]
def getAdult(self, i):
return self.movie[i]["adult"]
def getVersion(self, i):
return self.movie[i]["version"]
def getTranslated(self, i):
return self.movie[i]["translated"]
def getType(self, i):
return self.movie[i]["type"]
def getId(self, i):
return self.movie[i]["id"]
def getAlternativeName(self, i):
return self.movie[i]["alternative_name"]
def getPoster(self, i, size):
if size == "thumb" or size == "t":
return self.movie[i]["images"][0]["thumb"]
elif size == "cover" or size == "c":
return self.movie[i]["images"][0]["cover"]
else:
return self.movie[i]["images"][0]["mid"]
def getBackdrop(self, i, size):
if size == "poster" or size == "p":
return self.movie[i]["images"][1]["poster"]
else:
return self.movie[i]["images"][1]["thumb"]
# Shortcuts for imdb lookup method
# using:
# movie = tmdb.imdb("Sin City")
# print movie.getRating -> 7.0
class imdb:
def __init__(self, id = 0, title = False):
# get first movie if result=0
"""Convenience wrapper for MovieDb.search - so you can do..
>>> import tmdb
>>> movie = tmdb.imdb(title="Fight Club") # or movie = tmdb.imdb(id=imdb_id)
>>> ranking = movie.getRanking() or votes = movie.getVotes()
<Search results: [<MovieResult: Fight Club (1999-09-16)>]>
"""
self.id = id
self.title = title
self.mdb = MovieDb()
self.movie = self.mdb.imdbLookup(self.id, self.title)
def getRuntime(self, i):
return self.movie[i]["runtime"]
def getCategories(self):
from xml.dom.minidom import parse
adres = Config.getUrl('imdb.lookUp') % self.getImdbId()
d = parse(urllib2.urlopen(adres))
s = d.getElementsByTagName("categories")
ds = []
for i in range(len(s[0].childNodes)):
if i % 2 > 0:
ds.append(s[0].childNodes[i].getAttribute("name"))
return ds
def getRating(self, i):
return self.movie[i]["rating"]
def getVotes(self, i):
return self.movie[i]["votes"]
def getName(self, i):
return self.movie[i]["name"]
def getLanguage(self, i):
return self.movie[i]["language"]
def getCertification(self, i):
return self.movie[i]["certification"]
def getUrl(self, i):
return self.movie[i]["url"]
def getOverview(self, i):
return self.movie[i]["overview"]
def getPopularity(self, i):
return self.movie[i]["popularity"]
def getOriginalName(self, i):
return self.movie[i]["original_name"]
def getLastModified(self, i):
return self.movie[i]["last_modified_at"]
def getImdbId(self, i):
return self.movie[i]["imdb_id"]
def getReleased(self, i):
return self.movie[i]["released"]
def getAdult(self, i):
return self.movie[i]["adult"]
def getVersion(self, i):
return self.movie[i]["version"]
def getTranslated(self, i):
return self.movie[i]["translated"]
def getType(self, i):
return self.movie[i]["type"]
def getId(self, i):
return self.movie[i]["id"]
def getAlternativeName(self, i):
return self.movie[i]["alternative_name"]
def getPoster(self, i, size):
poster = []
if size == "thumb" or size == "t":
_size = "thumb"
elif size == "cover" or size == "c":
_size = "cover"
else:
_size = "mid"
for a in self.movie[i]["images"]:
if a["type"] == "poster":
poster.append(a[_size])
return poster
del poster
def getBackdrop(self, i, size):
backdrop = []
if size == "thumb" or size == "t":
_size = "thumb"
elif size == "cover" or size == "c":
_size = "cover"
else:
_size = "mid"
for a in self.movie[i]["images"]:
if a["type"] == "backdrop":
backdrop.append(a[_size])
return backdrop
del backdrop
def imdbLookup(id = 0, title = False):
"""Convenience wrapper for Imdb.Lookup - so you can do..
>>> import tmdb
>>> tmdb.imdbLookup("Fight Club")
<Search results: [<MovieResult: Fight Club (1999-09-16)>]>
"""
mdb = MovieDb()
return mdb.imdbLookup(id, title)
def search(name):
"""Convenience wrapper for MovieDb.search - so you can do..
>>> import tmdb
>>> tmdb.search("Fight Club")
<Search results: [<MovieResult: Fight Club (1999-09-16)>]>
"""
mdb = MovieDb()
return mdb.search(name)
def getMovieInfo(id):
"""Convenience wrapper for MovieDb.search - so you can do..
>>> import tmdb
>>> tmdb.getMovieInfo(187)
<MovieResult: Sin City (2005-04-01)>
"""
mdb = MovieDb()
return mdb.getMovieInfo(id)
def mediaGetInfo(hash, size):
"""Convenience wrapper for MovieDb.mediaGetInfo - so you can do..
>>> import tmdb
>>> tmdb.mediaGetInfo('907172e7fe51ba57', size = 742086656)[0]
<MovieResult: Sin City (2005-04-01)>
"""
mdb = MovieDb()
return mdb.mediaGetInfo(hash, size)
def searchByHashingFile(filename):
"""Searches for the specified file using the OpenSubtitle hashing method
"""
return mediaGetInfo(opensubtitleHashFile(filename), os.path.size(filename))