Renamer, movie searcher, Library scanning
New theme for UI and more..
This commit is contained in:
@@ -13,11 +13,10 @@ def toSafeString(original):
|
|||||||
|
|
||||||
|
|
||||||
def simplifyString(original):
|
def simplifyString(original):
|
||||||
string = toSafeString(original)
|
string = toSafeString(' '.join(re.split('\W+', original.lower())))
|
||||||
split = re.split('\W+', string.lower())
|
split = re.split('\W+', string.lower())
|
||||||
return toUnicode(' '.join(split))
|
return toUnicode(' '.join(split))
|
||||||
|
|
||||||
|
|
||||||
def toUnicode(original, *args):
|
def toUnicode(original, *args):
|
||||||
try:
|
try:
|
||||||
if type(original) is unicode:
|
if type(original) is unicode:
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ from couchpotato.core.plugins.base import Plugin
|
|||||||
from couchpotato.core.settings.model import FileType, File
|
from couchpotato.core.settings.model import FileType, File
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from flask.helpers import send_from_directory
|
from flask.helpers import send_from_directory
|
||||||
|
from sqlalchemy.sql.expression import or_
|
||||||
import os.path
|
import os.path
|
||||||
|
import traceback
|
||||||
import urllib2
|
import urllib2
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -51,15 +53,15 @@ class FileManager(Plugin):
|
|||||||
|
|
||||||
return dest
|
return dest
|
||||||
|
|
||||||
except Exception, e:
|
except Exception:
|
||||||
log.error('Unable to download file "%s": %s' % (url, e))
|
log.error('Unable to download file "%s": %s' % (url, traceback.format_exc()))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def add(self, path = '', part = 1, type = (), available = 1, properties = {}):
|
def add(self, path = '', part = 1, type = (), available = 1, properties = {}):
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
f = db.query(File).filter_by(path = toUnicode(path)).first()
|
f = db.query(File).filter(or_(File.path == toUnicode(path), File.path == path)).first()
|
||||||
if not f:
|
if not f:
|
||||||
f = File()
|
f = File()
|
||||||
db.add(f)
|
db.add(f)
|
||||||
@@ -78,7 +80,6 @@ class FileManager(Plugin):
|
|||||||
def getType(self, type):
|
def getType(self, type):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
type, identifier = type
|
type, identifier = type
|
||||||
|
|
||||||
ft = db.query(FileType).filter_by(identifier = identifier).first()
|
ft = db.query(FileType).filter_by(identifier = identifier).first()
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ var File = new Class({
|
|||||||
|
|
||||||
initialize: function(file){
|
initialize: function(file){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
if(!file){
|
||||||
|
self.el = new Element('div');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.data = file;
|
self.data = file;
|
||||||
self.type = File.Type.get(file.type_id);
|
self.type = File.Type.get(file.type_id);
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ class LibraryPlugin(Plugin):
|
|||||||
if update_after:
|
if update_after:
|
||||||
fireEventAsync('library.update', identifier = l.identifier, default_title = attrs.get('title', ''))
|
fireEventAsync('library.update', identifier = l.identifier, default_title = attrs.get('title', ''))
|
||||||
|
|
||||||
library_dict = l.to_dict()
|
return l.to_dict({'titles': {}, 'files':{}})
|
||||||
|
|
||||||
return library_dict
|
|
||||||
|
|
||||||
def update(self, identifier, default_title = '', force = False):
|
def update(self, identifier, default_title = '', force = False):
|
||||||
|
|
||||||
@@ -51,22 +49,27 @@ class LibraryPlugin(Plugin):
|
|||||||
library = db.query(Library).filter_by(identifier = identifier).first()
|
library = db.query(Library).filter_by(identifier = identifier).first()
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
|
|
||||||
|
library_dict = library.to_dict({'titles': {}, 'files':{}})
|
||||||
|
|
||||||
if library.status_id == done_status.get('id') and not force:
|
if library.status_id == done_status.get('id') and not force:
|
||||||
return
|
return library_dict
|
||||||
|
|
||||||
info = fireEvent('provider.movie.info', merge = True, identifier = identifier)
|
info = fireEvent('provider.movie.info', merge = True, identifier = identifier)
|
||||||
if not info or len(info) == 0:
|
if not info or len(info) == 0:
|
||||||
log.error('Could not update, no movie info to work with: %s' % identifier)
|
log.error('Could not update, no movie info to work with: %s' % identifier)
|
||||||
return
|
return library_dict
|
||||||
|
|
||||||
# Main info
|
# Main info
|
||||||
library.plot = info.get('plot', '')
|
library.plot = info.get('plot', '')
|
||||||
library.tagline = info.get('tagline', '')
|
library.tagline = info.get('tagline', '')
|
||||||
library.year = info.get('year', 0)
|
library.year = info.get('year', 0)
|
||||||
library.status_id = done_status.get('id')
|
library.status_id = done_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
# Titles
|
# Titles
|
||||||
[db.delete(title) for title in library.titles]
|
[db.delete(title) for title in library.titles]
|
||||||
|
db.commit()
|
||||||
|
|
||||||
titles = info.get('titles', [])
|
titles = info.get('titles', [])
|
||||||
|
|
||||||
log.debug('Adding titles: %s' % titles)
|
log.debug('Adding titles: %s' % titles)
|
||||||
@@ -83,6 +86,9 @@ class LibraryPlugin(Plugin):
|
|||||||
images = info.get('images', [])
|
images = info.get('images', [])
|
||||||
for type in images:
|
for type in images:
|
||||||
for image in images[type]:
|
for image in images[type]:
|
||||||
|
if not isinstance(image, str):
|
||||||
|
continue
|
||||||
|
|
||||||
file_path = fireEvent('file.download', url = image, single = True)
|
file_path = fireEvent('file.download', url = image, single = True)
|
||||||
file = fireEvent('file.add', path = file_path, type = ('image', type[:-1]), single = True)
|
file = fireEvent('file.add', path = file_path, type = ('image', type[:-1]), single = True)
|
||||||
try:
|
try:
|
||||||
@@ -94,3 +100,5 @@ class LibraryPlugin(Plugin):
|
|||||||
#log.debug('Failed to attach to library: %s' % traceback.format_exc())
|
#log.debug('Failed to attach to library: %s' % traceback.format_exc())
|
||||||
|
|
||||||
fireEvent('library.update.after')
|
fireEvent('library.update.after')
|
||||||
|
|
||||||
|
return library.to_dict({'titles': {}, 'files':{}})
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class MoviePlugin(Plugin):
|
|||||||
path = self.registerStatic(__file__)
|
path = self.registerStatic(__file__)
|
||||||
fireEvent('register_script', path + 'search.js')
|
fireEvent('register_script', path + 'search.js')
|
||||||
fireEvent('register_style', path + 'search.css')
|
fireEvent('register_style', path + 'search.css')
|
||||||
|
fireEvent('register_script', path + 'movie.js')
|
||||||
|
fireEvent('register_style', path + 'movie.css')
|
||||||
|
fireEvent('register_script', path + 'list.js')
|
||||||
|
|
||||||
def list(self):
|
def list(self):
|
||||||
|
|
||||||
@@ -35,7 +38,7 @@ class MoviePlugin(Plugin):
|
|||||||
movies = []
|
movies = []
|
||||||
for movie in results:
|
for movie in results:
|
||||||
temp = movie.to_dict(deep = {
|
temp = movie.to_dict(deep = {
|
||||||
'releases': {'status': {}, 'quality': {}},
|
'releases': {'status': {}, 'quality': {}, 'files':{}},
|
||||||
'library': {'titles': {}, 'files':{}},
|
'library': {'titles': {}, 'files':{}},
|
||||||
'files': {}
|
'files': {}
|
||||||
})
|
})
|
||||||
@@ -62,7 +65,13 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
if movie:
|
if movie:
|
||||||
#addEvent('library.update.after', )
|
#addEvent('library.update.after', )
|
||||||
fireEventAsync('library.update', library = movie.library, default_title = default_title)
|
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
|
||||||
|
fireEventAsync('searcher.single', movie.to_dict(deep = {
|
||||||
|
'profile': {'types': {'quality': {}}},
|
||||||
|
'releases': {'status': {}, 'quality': {}},
|
||||||
|
'library': {'titles': {}, 'files':{}},
|
||||||
|
'files': {}
|
||||||
|
}))
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
@@ -98,7 +107,7 @@ class MoviePlugin(Plugin):
|
|||||||
library = fireEvent('library.add', single = True, attrs = params)
|
library = fireEvent('library.add', single = True, attrs = params)
|
||||||
status = fireEvent('status.add', 'active', single = True)
|
status = fireEvent('status.add', 'active', single = True)
|
||||||
|
|
||||||
m = db.query(Movie).filter_by(library_id = library.id).first()
|
m = db.query(Movie).filter_by(library_id = library.get('id')).first()
|
||||||
if not m:
|
if not m:
|
||||||
m = Movie(
|
m = Movie(
|
||||||
library_id = library.get('id'),
|
library_id = library.get('id'),
|
||||||
@@ -108,7 +117,7 @@ class MoviePlugin(Plugin):
|
|||||||
|
|
||||||
m.status_id = status.get('id')
|
m.status_id = status.get('id')
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
movie_dict = m.to_dict(deep = {
|
movie_dict = m.to_dict(deep = {
|
||||||
'releases': {'status': {}, 'quality': {}},
|
'releases': {'status': {}, 'quality': {}},
|
||||||
'library': {'titles': {}}
|
'library': {'titles': {}}
|
||||||
@@ -121,7 +130,22 @@ class MoviePlugin(Plugin):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def edit(self):
|
def edit(self):
|
||||||
pass
|
|
||||||
|
params = getParams()
|
||||||
|
db = get_session();
|
||||||
|
|
||||||
|
m = db.query(Movie).filter_by(id = params.get('id')).first()
|
||||||
|
m.profile_id = params.get('profile_id')
|
||||||
|
|
||||||
|
# Default title
|
||||||
|
for title in m.library.titles:
|
||||||
|
title.default = params.get('default_title').lower() == title.title.lower()
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return jsonified({
|
||||||
|
'success': True,
|
||||||
|
})
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
var MovieList = new Class({
|
||||||
|
|
||||||
|
Implements: [Options],
|
||||||
|
|
||||||
|
options: {
|
||||||
|
navigation: true
|
||||||
|
},
|
||||||
|
|
||||||
|
movies: [],
|
||||||
|
|
||||||
|
initialize: function(options){
|
||||||
|
var self = this;
|
||||||
|
self.setOptions(options);
|
||||||
|
|
||||||
|
self.el = new Element('div.movies');
|
||||||
|
self.getMovies();
|
||||||
|
},
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Create the alphabet nav
|
||||||
|
if(self.options.navigation)
|
||||||
|
self.createNavigation();
|
||||||
|
|
||||||
|
Object.each(self.movies, function(info){
|
||||||
|
var m = new Movie(self, {
|
||||||
|
'actions': self.options.actions
|
||||||
|
}, info);
|
||||||
|
$(m).inject(self.el);
|
||||||
|
m.fireEvent('injected');
|
||||||
|
});
|
||||||
|
|
||||||
|
self.el.addEvents({
|
||||||
|
'mouseenter:relay(.movie)': function(e, el){
|
||||||
|
el.addClass('hover')
|
||||||
|
},
|
||||||
|
'mouseleave:relay(.movie)': function(e, el){
|
||||||
|
el.removeClass('hover')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createNavigation: function(){
|
||||||
|
var self = this;
|
||||||
|
var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
var selected = 'Z';
|
||||||
|
|
||||||
|
self.navigation = new Element('div.alph_nav').adopt(
|
||||||
|
self.alpha = new Element('ul.inlay'),
|
||||||
|
self.input = new Element('input.inlay'),
|
||||||
|
self.view = new Element('ul.inlay').adopt(
|
||||||
|
new Element('li.list'),
|
||||||
|
new Element('li.thumbnails'),
|
||||||
|
new Element('li.text')
|
||||||
|
)
|
||||||
|
).inject(this.el, 'top')
|
||||||
|
|
||||||
|
chars.split('').each(function(c){
|
||||||
|
new Element('li', {
|
||||||
|
'text': c,
|
||||||
|
'class': c == selected ? 'selected' : ''
|
||||||
|
}).inject(self.alpha)
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
getMovies: function(status, onComplete){
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
if(self.movies.length == 0)
|
||||||
|
Api.request('movie.list', {
|
||||||
|
'data': {
|
||||||
|
'status': self.options.status
|
||||||
|
},
|
||||||
|
'onComplete': function(json){
|
||||||
|
self.store(json.movies);
|
||||||
|
self.create();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else
|
||||||
|
self.list()
|
||||||
|
},
|
||||||
|
|
||||||
|
store: function(movies){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.movies = movies;
|
||||||
|
},
|
||||||
|
|
||||||
|
toElement: function(){
|
||||||
|
return this.el;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
/* @override http://localhost:5000/static/movie_plugin/movie.css */
|
||||||
|
|
||||||
|
.movies {
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.movies .movie:hover {
|
||||||
|
border-color: #ddd #fff #fff #ddd;
|
||||||
|
}
|
||||||
|
.movies .movie:hover .data {
|
||||||
|
}
|
||||||
|
.movies .data {
|
||||||
|
padding: 2%;
|
||||||
|
position: absolute;
|
||||||
|
width: 96.1%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
}
|
||||||
|
.movies .data:after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 0;
|
||||||
|
clear: both;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.movies .data .poster, .options .poster {
|
||||||
|
overflow: hidden;
|
||||||
|
float: left;
|
||||||
|
max-width: 10%;
|
||||||
|
margin: 0 2% 0 0;
|
||||||
|
border-radius:3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.35);
|
||||||
|
-moz-box-shadow: 0 0 10px rgba(0,0,0,0.35);
|
||||||
|
-webkit-box-shadow: 0 0 10px rgba(0,0,0,0.35);
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .info {
|
||||||
|
float: right;
|
||||||
|
width: 88%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .info .title {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
float: left;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .info .year {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
float: right;
|
||||||
|
color: #bbb;
|
||||||
|
width: 10%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .info .rating {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #444;
|
||||||
|
float: left;
|
||||||
|
width: 5%;
|
||||||
|
padding: 0 0 0 3%;
|
||||||
|
background: url('../images/rating.png') no-repeat left center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .info .description {
|
||||||
|
clear: both;
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
.movies .data .actions {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 15px;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
.movies .data:hover .action { opacity: 0.6; }
|
||||||
|
.movies .data:hover .action:hover { opacity: 1; }
|
||||||
|
|
||||||
|
.movies .data .action {
|
||||||
|
background: no-repeat center;
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 3px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.movies .data .action.refresh { background-image: url('../images/reload.png'); }
|
||||||
|
.movies .data .action.delete { background-image: url('../images/delete.png'); }
|
||||||
|
.movies .data .action.edit { background-image: url('../images/edit.png'); }
|
||||||
|
.movies .data .action.imdb { background-image: url('../images/imdb.png'); }
|
||||||
|
|
||||||
|
.movies .delete_container {
|
||||||
|
clear: both;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.movies .delete_container .cancel {
|
||||||
|
}
|
||||||
|
.movies .delete_container .or {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.movies .delete_container .delete {
|
||||||
|
background-color: #ff321c;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.movies .delete_container .delete:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #d32917;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .options .form {
|
||||||
|
margin-top: -2%;
|
||||||
|
float: left;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .options .form select {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .options {
|
||||||
|
padding: 2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .alph_nav ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .alph_nav li {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 26px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 -1px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .alph_nav li:hover, .movies .alph_nav li.onlay {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
var Movie = new Class({
|
||||||
|
|
||||||
|
Extends: BlockBase,
|
||||||
|
|
||||||
|
action: {},
|
||||||
|
|
||||||
|
initialize: function(self, options, data){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.data = data;
|
||||||
|
|
||||||
|
self.profile = Quality.getProfile(data.profile_id);
|
||||||
|
self.parent(self, options);
|
||||||
|
self.addEvent('injected', self.afterInject.bind(self))
|
||||||
|
},
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el = new Element('div.movie.inlay').adopt(
|
||||||
|
self.data_container = new Element('div.data.inlay.light', {
|
||||||
|
'tween': {
|
||||||
|
duration: 400,
|
||||||
|
transition: 'quint:in:out'
|
||||||
|
}
|
||||||
|
}).adopt(
|
||||||
|
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
||||||
|
self.info_container = new Element('div.info').adopt(
|
||||||
|
self.title = new Element('div.title', {
|
||||||
|
'text': self.getTitle()
|
||||||
|
}),
|
||||||
|
self.year = new Element('div.year', {
|
||||||
|
'text': self.data.library.year || 'Unknown'
|
||||||
|
}),
|
||||||
|
self.rating = new Element('div.rating', {
|
||||||
|
'text': self.data.library.rating
|
||||||
|
}),
|
||||||
|
self.description = new Element('div.description', {
|
||||||
|
'text': self.data.library.plot
|
||||||
|
}),
|
||||||
|
self.quality = new Element('div.quality', {
|
||||||
|
'text': self.profile ? self.profile.get('label') : ''
|
||||||
|
})
|
||||||
|
),
|
||||||
|
self.actions = new Element('div.actions')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Object.each(self.options.actions, function(action, key){
|
||||||
|
self.actions.adopt(
|
||||||
|
self.action[key.toLowerCase()] = new self.options.actions[key](self)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!self.data.library.rating)
|
||||||
|
self.rating.hide();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
afterInject: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var height = self.getHeight();
|
||||||
|
self.el.setStyle('height', height);
|
||||||
|
},
|
||||||
|
|
||||||
|
getTitle: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var titles = self.data.library.titles;
|
||||||
|
|
||||||
|
var title = titles.filter(function(title){
|
||||||
|
return title['default']
|
||||||
|
}).pop()
|
||||||
|
|
||||||
|
if(title)
|
||||||
|
return title.title
|
||||||
|
else if(titles.length > 0)
|
||||||
|
return titles[0].title
|
||||||
|
|
||||||
|
return 'Unknown movie'
|
||||||
|
},
|
||||||
|
|
||||||
|
slide: function(direction){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(direction == 'in'){
|
||||||
|
self.el.addEvent('outerClick', self.slide.bind(self, 'out'))
|
||||||
|
self.data_container.tween('left', 0, self.getWidth());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.el.removeEvents('outerClick')
|
||||||
|
self.data_container.tween('left', self.getWidth(), 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getHeight: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(!self.height)
|
||||||
|
self.height = self.data_container.getCoordinates().height;
|
||||||
|
|
||||||
|
return self.height;
|
||||||
|
},
|
||||||
|
|
||||||
|
getWidth: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(!self.width)
|
||||||
|
self.width = self.data_container.getCoordinates().width;
|
||||||
|
|
||||||
|
return self.width;
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(attr){
|
||||||
|
return this.data[attr] || this.data.library[attr]
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var MovieAction = new Class({
|
||||||
|
|
||||||
|
class_name: 'action',
|
||||||
|
|
||||||
|
initialize: function(movie){
|
||||||
|
var self = this;
|
||||||
|
self.movie = movie;
|
||||||
|
|
||||||
|
self.create();
|
||||||
|
self.el.addClass(self.class_name)
|
||||||
|
},
|
||||||
|
|
||||||
|
create: function(){},
|
||||||
|
|
||||||
|
disable: function(){
|
||||||
|
this.el.addClass('disable')
|
||||||
|
},
|
||||||
|
|
||||||
|
enable: function(){
|
||||||
|
this.el.removeClass('disable')
|
||||||
|
},
|
||||||
|
|
||||||
|
toElement: function(){
|
||||||
|
return this.el
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var IMDBAction = new Class({
|
||||||
|
|
||||||
|
Extends: MovieAction,
|
||||||
|
id: null,
|
||||||
|
|
||||||
|
create: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.id = self.movie.get('identifier');
|
||||||
|
|
||||||
|
self.el = new Element('a.imdb', {
|
||||||
|
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
|
||||||
|
'events': {
|
||||||
|
'click': self.gotoIMDB.bind(self)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!self.id) self.disable();
|
||||||
|
},
|
||||||
|
|
||||||
|
gotoIMDB: function(e){
|
||||||
|
var self = this;
|
||||||
|
(e).stop();
|
||||||
|
|
||||||
|
window.open('http://www.imdb.com/title/'+self.id+'/');
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* @override http://localhost:5000/static/style/plugin/movie_add.css */
|
/* @override http://localhost:5000/static/movie_plugin/search.css */
|
||||||
|
|
||||||
.search_form {
|
.search_form {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -7,52 +7,46 @@
|
|||||||
|
|
||||||
.search_form input {
|
.search_form input {
|
||||||
padding-right: 25px;
|
padding-right: 25px;
|
||||||
border: 1px solid #aaa;
|
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|
||||||
border-radius: 3px;
|
|
||||||
-webkit-border-radius: 3px;
|
|
||||||
-moz-border-radius: 3px;
|
|
||||||
}
|
}
|
||||||
.search_form .input a {
|
.search_form .input a {
|
||||||
width: 12px;
|
width: 17px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 0 -5px -20px;
|
margin: 0 0 -5px -20px;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
background: url('../../images/close_button.png') 0 center no-repeat;
|
background: url('../images/checks.png') right -36px no-repeat;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.search_form .input a:hover { background-position: -12px center; }
|
|
||||||
|
|
||||||
.search_form .results_container {
|
.search_form .results_container {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: #fff;
|
background: #5c697b;
|
||||||
margin: 11px 0 0 -243px;
|
margin: 6px 0 0 -246px;
|
||||||
width: 470px;
|
width: 470px;
|
||||||
min-height: 140px;
|
min-height: 140px;
|
||||||
|
|
||||||
box-shadow: 0 0 30px rgba(0,0,0,0.2);
|
|
||||||
-moz-box-shadow: 0 0 30px rgba(0,0,0,0.2);
|
|
||||||
-webkit-box-shadow: 0 0 30px rgba(0,0,0,0.2);
|
|
||||||
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
-webkit-border-radius: 3px;
|
-webkit-border-radius: 3px;
|
||||||
-moz-border-radius: 3px;
|
-moz-border-radius: 3px;
|
||||||
|
|
||||||
|
box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
||||||
|
-moz-box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
||||||
|
-webkit-box-shadow: 0 0 50px rgba(0,0,0,0.55);
|
||||||
}
|
}
|
||||||
.search_form .spinner {
|
.search_form .spinner {
|
||||||
background: #fff url('../../images/spinner.gif') no-repeat center 70px;
|
background: rgba(0,0,0,0.8) url('../images/spinner.gif') no-repeat center 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_form .pointer {
|
.search_form .pointer {
|
||||||
border-right: 10px solid transparent;
|
border-right: 10px solid transparent;
|
||||||
border-left: 10px solid transparent;
|
border-left: 10px solid transparent;
|
||||||
border-bottom: 10px solid #fff;
|
border-bottom: 10px solid #5c697b;
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 0px;
|
width: 0px;
|
||||||
@@ -62,16 +56,24 @@
|
|||||||
|
|
||||||
.search_form .results .movie {
|
.search_form .results .movie {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #666;
|
|
||||||
min-height: 140px;
|
min-height: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_form .results .movie .options {
|
.search_form .results .movie .options {
|
||||||
height: 140px;
|
height: 139px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-width: 1px 0;
|
||||||
|
border-radius: 0;
|
||||||
|
-webkit-border-radius: 0;
|
||||||
|
-moz-border-radius: 0;
|
||||||
|
box-shadow: inset 0 1px 8px rgba(0,0,0,0.25);
|
||||||
|
-moz-box-shadow: inset 0 1px 8px rgba(0,0,0,0.25);
|
||||||
|
-webkit-box-shadow: inset 0 1px 8px rgba(0,0,0,0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_form .results .movie .options > div {
|
.search_form .results .movie .options > div {
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_form .results .movie .options .thumbnail {
|
.search_form .results .movie .options .thumbnail {
|
||||||
@@ -105,11 +107,17 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
top: 0;
|
top: 0;
|
||||||
margin: -140px 0 0 0;
|
margin: -143px 0 0 0;
|
||||||
background: #fff;
|
|
||||||
min-height: 140px;
|
min-height: 140px;
|
||||||
|
background: #5c697b;
|
||||||
|
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
border-top: 1px solid rgba(255,255,255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search_form .results .movie:first-child .data { border-top: 0; }
|
||||||
|
.search_form .results .movie:last-child .data { border-bottom: 0; }
|
||||||
|
|
||||||
.search_form .results .movie .thumbnail {
|
.search_form .results .movie .thumbnail {
|
||||||
width: 17%;
|
width: 17%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -129,7 +137,6 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
padding: 15px 0;
|
padding: 15px 0;
|
||||||
background: #fff;
|
|
||||||
}
|
}
|
||||||
.search_form .results .movie .add +.info {
|
.search_form .results .movie .add +.info {
|
||||||
margin-left: 20%;
|
margin-left: 20%;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Block.Search = new Class({
|
|||||||
|
|
||||||
self.el = new Element('div.search_form').adopt(
|
self.el = new Element('div.search_form').adopt(
|
||||||
new Element('div.input').adopt(
|
new Element('div.input').adopt(
|
||||||
self.input = new Element('input', {
|
self.input = new Element('input.inlay', {
|
||||||
'events': {
|
'events': {
|
||||||
'keyup': self.keyup.bind(self),
|
'keyup': self.keyup.bind(self),
|
||||||
'focus': self.hideResults.bind(self, false)
|
'focus': self.hideResults.bind(self, false)
|
||||||
@@ -164,7 +164,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(
|
||||||
self.options = new Element('div.options'),
|
self.options = new Element('div.options.inlay'),
|
||||||
self.data_container = new Element('div.data', {
|
self.data_container = new Element('div.data', {
|
||||||
'tween': {
|
'tween': {
|
||||||
duration: 400,
|
duration: 400,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.request import jsonified, getParams, getParam
|
from couchpotato.core.helpers.request import jsonified, getParams, getParam
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
@@ -17,6 +17,10 @@ class ProfilePlugin(Plugin):
|
|||||||
addApiView('profile.save', self.save)
|
addApiView('profile.save', self.save)
|
||||||
addApiView('profile.delete', self.delete)
|
addApiView('profile.delete', self.delete)
|
||||||
|
|
||||||
|
path = self.registerStatic(__file__)
|
||||||
|
fireEvent('register_script', path + 'profile.js')
|
||||||
|
fireEvent('register_style', path + 'profile.css')
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
@@ -59,7 +63,7 @@ class ProfilePlugin(Plugin):
|
|||||||
order += 1
|
order += 1
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
profile_dict = p.to_dict(deep = {'types': {}})
|
profile_dict = p.to_dict(deep = {'types': {}})
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.profile > .delete {
|
||||||
|
background-position: center;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .types .type .handle {
|
||||||
|
background: url('../../images/handle.png') center;
|
||||||
|
display: inline-block;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .types .type .delete {
|
||||||
|
background-position: center;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
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.profile').adopt(
|
||||||
|
self.header = new Element('h4', {'text': data.label}),
|
||||||
|
new Element('span.delete.icon', {
|
||||||
|
'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,
|
||||||
|
'events': {
|
||||||
|
'keyup': function(){
|
||||||
|
self.header.set('text', this.get('value'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
new Element('div.ctrlHolder').adopt(
|
||||||
|
new Element('label', {'text':'Wait'}),
|
||||||
|
new Element('input.wait_for.textInput.xsmall', {
|
||||||
|
'type':'text',
|
||||||
|
'value': data.types && data.types.length > 0 ? data.types[0].wait_for : 0
|
||||||
|
}),
|
||||||
|
new Element('span', {'text':' day(s) for better quality.'})
|
||||||
|
),
|
||||||
|
new Element('div.ctrlHolder').adopt(
|
||||||
|
new Element('label', {'text': 'Qualities'}),
|
||||||
|
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>'})
|
||||||
|
),
|
||||||
|
self.type_container = new Element('ol.types'),
|
||||||
|
new Element('a.addType', {
|
||||||
|
'text': 'Add another quality to search for.',
|
||||||
|
'href': '#',
|
||||||
|
'events': {
|
||||||
|
'click': self.addType.bind(self)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
self.makeSortable()
|
||||||
|
|
||||||
|
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(){
|
||||||
|
|
||||||
|
var data = self.getData();
|
||||||
|
if(data.types.length < 2) return;
|
||||||
|
|
||||||
|
Api.request('profile.save', {
|
||||||
|
'data': self.getData(),
|
||||||
|
'useSpinner': true,
|
||||||
|
'spinnerOptions': {
|
||||||
|
'target': self.el
|
||||||
|
},
|
||||||
|
'onComplete': function(json){
|
||||||
|
if(json.success){
|
||||||
|
self.data = json.profile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).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': []
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.each(self.type_container.getElements('.type'), function(type){
|
||||||
|
if(!type.hasClass('deleted'))
|
||||||
|
data.types.include({
|
||||||
|
'quality_id': type.getElement('select').get('value'),
|
||||||
|
'finish': +type.getElement('input[type=checkbox]').checked
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
|
||||||
|
addType: function(data){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var t = new Profile.Type(data);
|
||||||
|
$(t).inject(self.type_container);
|
||||||
|
self.sortable.addItems($(t));
|
||||||
|
|
||||||
|
self.types.include(t);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
del: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(!confirm('Are you sure you want to delete this profile?')) return
|
||||||
|
|
||||||
|
Api.request('profile.delete', {
|
||||||
|
'data': {
|
||||||
|
'id': self.data.id
|
||||||
|
},
|
||||||
|
'useSpinner': true,
|
||||||
|
'spinnerOptions': {
|
||||||
|
'target': self.el
|
||||||
|
},
|
||||||
|
'onComplete': function(json){
|
||||||
|
if(json.success)
|
||||||
|
self.el.destroy();
|
||||||
|
else
|
||||||
|
alert(json.message)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
makeSortable: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.sortable = new Sortables(self.type_container, {
|
||||||
|
'revert': true,
|
||||||
|
//'clone': true,
|
||||||
|
'handle': '.handle',
|
||||||
|
'opacity': 0.5,
|
||||||
|
'onComplete': self.save.bind(self, 300)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(attr){
|
||||||
|
return this.data[attr]
|
||||||
|
},
|
||||||
|
|
||||||
|
isCore: function(){
|
||||||
|
return this.data.core
|
||||||
|
},
|
||||||
|
|
||||||
|
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('li.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.icon', {
|
||||||
|
'events': {
|
||||||
|
'click': self.del.bind(self)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new Element('span.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.id
|
||||||
|
}).inject(self.qualities)
|
||||||
|
});
|
||||||
|
|
||||||
|
self.qualities.set('value', self.data.quality_id);
|
||||||
|
|
||||||
|
return self.qualities;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
getData: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
'quality_id': self.qualities.get('value'),
|
||||||
|
'finish': +self.finish.checked
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
del: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el.addClass('deleted');
|
||||||
|
self.el.hide();
|
||||||
|
self.deleted = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
toElement: function(){
|
||||||
|
return this.el;
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
@@ -4,6 +4,8 @@ from couchpotato.core.helpers.encoding import toUnicode
|
|||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Quality, Profile, ProfileType
|
from couchpotato.core.settings.model import Quality, Profile, ProfileType
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -11,10 +13,10 @@ log = CPLog(__name__)
|
|||||||
class QualityPlugin(Plugin):
|
class QualityPlugin(Plugin):
|
||||||
|
|
||||||
qualities = [
|
qualities = [
|
||||||
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['1080p', 'bd25'], 'allow': [], 'ext':[], 'tags': ['x264', 'h264', 'blu ray']},
|
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['1080p', 'bd25'], 'allow': [], 'ext':[], 'tags': ['x264', 'h264', 'blu ray']},
|
||||||
{'identifier': '1080p', 'size': (5000, 20000), 'label': '1080P', 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
|
{'identifier': '1080p', 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
|
||||||
{'identifier': '720p', 'size': (3500, 10000), 'label': '720P', 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
|
{'identifier': '720p', 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
|
||||||
{'identifier': 'brrip', 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['mkv', 'avi']},
|
{'identifier': 'brrip', 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
|
||||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc']},
|
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc']},
|
||||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['dvdscr'], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['dvdscr'], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
@@ -28,6 +30,7 @@ class QualityPlugin(Plugin):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('quality.all', self.all)
|
addEvent('quality.all', self.all)
|
||||||
addEvent('quality.single', self.single)
|
addEvent('quality.single', self.single)
|
||||||
|
addEvent('quality.guess', self.guess)
|
||||||
addEvent('app.load', self.fill)
|
addEvent('app.load', self.fill)
|
||||||
|
|
||||||
path = self.registerStatic(__file__)
|
path = self.registerStatic(__file__)
|
||||||
@@ -50,9 +53,11 @@ class QualityPlugin(Plugin):
|
|||||||
def single(self, identifier = ''):
|
def single(self, identifier = ''):
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
quality_dict = {}
|
||||||
|
|
||||||
quality = db.query(Quality).filter_by(identifier = identifier).first()
|
quality = db.query(Quality).filter_by(identifier = identifier).first()
|
||||||
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
|
if quality:
|
||||||
|
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
|
||||||
|
|
||||||
return quality_dict
|
return quality_dict
|
||||||
|
|
||||||
@@ -110,3 +115,41 @@ class QualityPlugin(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def guess(self, files, extra = {}):
|
||||||
|
found = False
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
size = (os.path.getsize(file) / 1024 / 1024)
|
||||||
|
words = re.split('\W+', file.lower())
|
||||||
|
for quality in self.all():
|
||||||
|
correctSize = False
|
||||||
|
|
||||||
|
if size >= quality['size_min'] and size <= quality['size_max']:
|
||||||
|
correctSize = True
|
||||||
|
|
||||||
|
# Check tags
|
||||||
|
if type in words:
|
||||||
|
found = True
|
||||||
|
|
||||||
|
for alt in quality.get('alternative'):
|
||||||
|
if alt in words:
|
||||||
|
found = True
|
||||||
|
|
||||||
|
for tag in quality.get('tags', []):
|
||||||
|
if tag in words:
|
||||||
|
found = True
|
||||||
|
|
||||||
|
# Check extension + filesize
|
||||||
|
for ext in quality.get('ext'):
|
||||||
|
if ext in words and correctSize:
|
||||||
|
found = True
|
||||||
|
|
||||||
|
# Last check on resolution only
|
||||||
|
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
||||||
|
found = True
|
||||||
|
|
||||||
|
if found:
|
||||||
|
return quality
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
/* @override http://localhost:5000/static/style/plugin/quality.css */
|
|
||||||
|
|
||||||
|
|
||||||
.profile > .delete {
|
|
||||||
background-position: center;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile .types .type .handle {
|
|
||||||
background: url('../../images/handle.png') center;
|
|
||||||
display: inline-block;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile .types .type .delete {
|
|
||||||
background-position: center;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
@@ -33,7 +33,6 @@ var QualityBase = new Class({
|
|||||||
self.content = tab.content;
|
self.content = tab.content;
|
||||||
|
|
||||||
self.createProfiles();
|
self.createProfiles();
|
||||||
self.createOrdering();
|
|
||||||
self.createSizes();
|
self.createSizes();
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -83,291 +82,36 @@ var QualityBase = new Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Ordering
|
|
||||||
*/
|
|
||||||
createOrdering: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.settings.createGroup({
|
|
||||||
'label': 'Order',
|
|
||||||
'description': 'Discriptions'
|
|
||||||
}).inject(self.content)
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sizes
|
* Sizes
|
||||||
*/
|
*/
|
||||||
createSizes: function(){
|
createSizes: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.settings.createGroup({
|
var group = self.settings.createGroup({
|
||||||
'label': 'Sizes',
|
'label': 'Sizes',
|
||||||
'description': 'Discriptions',
|
'description': 'Discriptions',
|
||||||
'advanced': true
|
'advanced': true
|
||||||
}).inject(self.content)
|
}).inject(self.content)
|
||||||
}
|
|
||||||
|
new Element('div.item.header').adopt(
|
||||||
});
|
new Element('span.label', {'text': 'Quality'}),
|
||||||
window.Quality = new QualityBase();
|
new Element('span.min', {'text': 'Min'}),
|
||||||
|
new Element('span.max', {'text': 'Max'})
|
||||||
var Profile = new Class({
|
).inject(group)
|
||||||
|
|
||||||
data: {},
|
Object.each(self.qualities, function(quality){
|
||||||
types: [],
|
new Element('div.item').adopt(
|
||||||
|
new Element('span.label', {'text': quality.label}),
|
||||||
initialize: function(data){
|
new Element('input.min', {'value': quality.size_min}),
|
||||||
var self = this;
|
new Element('input.max', {'value': quality.size_max})
|
||||||
|
).inject(group)
|
||||||
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.profile').adopt(
|
|
||||||
self.header = new Element('h4', {'text': data.label}),
|
|
||||||
new Element('span.delete.icon', {
|
|
||||||
'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,
|
|
||||||
'events': {
|
|
||||||
'keyup': function(){
|
|
||||||
self.header.set('text', this.get('value'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
),
|
|
||||||
new Element('div.ctrlHolder').adopt(
|
|
||||||
new Element('label', {'text':'Wait'}),
|
|
||||||
new Element('input.wait_for.textInput.xsmall', {
|
|
||||||
'type':'text',
|
|
||||||
'value': data.types && data.types.length > 0 ? data.types[0].wait_for : 0
|
|
||||||
}),
|
|
||||||
new Element('span', {'text':' day(s) for better quality.'})
|
|
||||||
),
|
|
||||||
new Element('div.ctrlHolder').adopt(
|
|
||||||
new Element('label', {'text': 'Qualities'}),
|
|
||||||
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>'})
|
|
||||||
),
|
|
||||||
self.type_container = new Element('ol.types'),
|
|
||||||
new Element('a.addType', {
|
|
||||||
'text': 'Add another quality to search for.',
|
|
||||||
'href': '#',
|
|
||||||
'events': {
|
|
||||||
'click': self.addType.bind(self)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
self.makeSortable()
|
|
||||||
|
|
||||||
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(){
|
|
||||||
|
|
||||||
var data = self.getData();
|
|
||||||
if(data.types.length < 2) return;
|
|
||||||
|
|
||||||
Api.request('profile.save', {
|
|
||||||
'data': self.getData(),
|
|
||||||
'useSpinner': true,
|
|
||||||
'spinnerOptions': {
|
|
||||||
'target': self.el
|
|
||||||
},
|
|
||||||
'onComplete': function(json){
|
|
||||||
if(json.success){
|
|
||||||
self.data = json.profile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).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': []
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.each(self.type_container.getElements('.type'), function(type){
|
|
||||||
if(!type.hasClass('deleted'))
|
|
||||||
data.types.include({
|
|
||||||
'quality_id': type.getElement('select').get('value'),
|
|
||||||
'finish': +type.getElement('input[type=checkbox]').checked
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return data
|
p(group)
|
||||||
},
|
|
||||||
|
|
||||||
addType: function(data){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var t = new Profile.Type(data);
|
|
||||||
$(t).inject(self.type_container);
|
|
||||||
self.sortable.addItems($(t));
|
|
||||||
|
|
||||||
self.types.include(t);
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
del: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if(!confirm('Are you sure you want to delete this profile?')) return
|
|
||||||
|
|
||||||
Api.request('profile.delete', {
|
|
||||||
'data': {
|
|
||||||
'id': self.data.id
|
|
||||||
},
|
|
||||||
'useSpinner': true,
|
|
||||||
'spinnerOptions': {
|
|
||||||
'target': self.el
|
|
||||||
},
|
|
||||||
'onComplete': function(json){
|
|
||||||
if(json.success)
|
|
||||||
self.el.destroy();
|
|
||||||
else
|
|
||||||
alert(json.message)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
makeSortable: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.sortable = new Sortables(self.type_container, {
|
|
||||||
'revert': true,
|
|
||||||
//'clone': true,
|
|
||||||
'handle': '.handle',
|
|
||||||
'opacity': 0.5,
|
|
||||||
'onComplete': self.save.bind(self, 300)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
get: function(attr){
|
|
||||||
return this.data[attr]
|
|
||||||
},
|
|
||||||
|
|
||||||
isCore: function(){
|
|
||||||
return this.data.core
|
|
||||||
},
|
|
||||||
|
|
||||||
toElement: function(){
|
|
||||||
return this.el
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Profile.Type = Class({
|
window.Quality = new QualityBase();
|
||||||
|
|
||||||
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('li.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.icon', {
|
|
||||||
'events': {
|
|
||||||
'click': self.del.bind(self)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new Element('span.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.id
|
|
||||||
}).inject(self.qualities)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.qualities.set('value', self.data.quality_id);
|
|
||||||
|
|
||||||
return self.qualities;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
getData: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
return {
|
|
||||||
'quality_id': self.qualities.get('value'),
|
|
||||||
'finish': +self.finish.checked
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
del: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.el.addClass('deleted');
|
|
||||||
self.el.hide();
|
|
||||||
self.deleted = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
toElement: function(){
|
|
||||||
return this.el;
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ config = [{
|
|||||||
'groups': [
|
'groups': [
|
||||||
{
|
{
|
||||||
'tab': 'renamer',
|
'tab': 'renamer',
|
||||||
'name': 'tmdb',
|
'name': 'renamer',
|
||||||
'label': 'TheMovieDB',
|
'label': 'Folders',
|
||||||
'advanced': True,
|
|
||||||
'description': 'Move and rename your downloaded movies to your movie directory.',
|
'description': 'Move and rename your downloaded movies to your movie directory.',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
@@ -29,12 +28,52 @@ config = [{
|
|||||||
'description': 'Folder where the movies will be moved to.',
|
'description': 'Folder where the movies will be moved to.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
'name': 'folder_name',
|
||||||
|
'label': 'Folder naming',
|
||||||
|
'description': 'Name of the folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'file_name',
|
||||||
|
'label': 'File naming',
|
||||||
|
'description': 'Name of the file',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'advanced': True,
|
||||||
|
'name': 'separator',
|
||||||
|
'label': 'Separator',
|
||||||
|
'description': 'Replace all the spaces with a character. Example: ".", "-". Leave empty to use spaces.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'advanced': True,
|
||||||
'name': 'run_every',
|
'name': 'run_every',
|
||||||
'label': 'Run every',
|
'label': 'Run every',
|
||||||
'default': 1,
|
'default': 1,
|
||||||
'type': 'int',
|
'type': 'int',
|
||||||
'unit': 'min(s)',
|
'unit': 'min(s)',
|
||||||
'description': 'Search for new movies inside the folder every X minutes.',
|
'description': 'Search for new movies inside the folder every X minutes.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
'tab': 'renamer',
|
||||||
|
'name': 'meta_renamer',
|
||||||
|
'label': 'Advanced renaming',
|
||||||
|
'description': 'Meta data file renaming. Use <filename> to use the above "File naming" settings, without the file extention.',
|
||||||
|
'advanced': True,
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'trailer_name',
|
||||||
|
'label': 'Trailer naming',
|
||||||
|
'default': '<filename>-trailer.<ext>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'nfo_name',
|
||||||
|
'label': 'NFO naming',
|
||||||
|
'default': '<filename>.<ext>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'backdrop_name',
|
||||||
|
'label': 'Backdrop naming',
|
||||||
|
'default': '<filename>-backdrop.<ext>',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
|
from couchpotato import get_session
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
|
from couchpotato.core.helpers.variable import getExt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
|
from couchpotato.core.settings.model import Library
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -8,12 +16,208 @@ log = CPLog(__name__)
|
|||||||
class Renamer(Plugin):
|
class Renamer(Plugin):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
|
||||||
|
|
||||||
addEvent('renamer.scan', self.scan)
|
addEvent('renamer.scan', self.scan)
|
||||||
addEvent('app.load', self.scan)
|
#addEvent('app.load', self.scan)
|
||||||
|
|
||||||
#fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every'))
|
#fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every'))
|
||||||
|
|
||||||
def scan(self):
|
def scan(self):
|
||||||
pass
|
|
||||||
|
groups = fireEvent('scanner.scan', folder = self.conf('from'), single = True)
|
||||||
|
|
||||||
|
destination = self.conf('to')
|
||||||
|
folder_name = self.conf('folder_name')
|
||||||
|
file_name = self.conf('file_name')
|
||||||
|
trailer_name = self.conf('trailer_name')
|
||||||
|
backdrop_name = self.conf('fanart_name')
|
||||||
|
nfo_name = self.conf('nfo_name')
|
||||||
|
separator = self.conf('separator')
|
||||||
|
|
||||||
|
for group_identifier in groups:
|
||||||
|
|
||||||
|
group = groups[group_identifier]
|
||||||
|
rename_files = {}
|
||||||
|
|
||||||
|
# Add _UNKNOWN_ if no library is connected
|
||||||
|
if not group['library']:
|
||||||
|
if group['dirname']:
|
||||||
|
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_UNKNOWN_%s' % group['dirname'])
|
||||||
|
else: # Add it to filename
|
||||||
|
for file_type in group['files']:
|
||||||
|
for rename_me in group['files'][file_type]:
|
||||||
|
filename = os.path.basename(rename_me)
|
||||||
|
rename_files[rename_me] = rename_me.replace(filename, '_UNKNOWN_%s' % filename)
|
||||||
|
|
||||||
|
# Rename the files using the library data
|
||||||
|
else:
|
||||||
|
group['library'] = fireEvent('library.update', identifier = group['library']['identifier'], single = True)
|
||||||
|
library = group['library']
|
||||||
|
|
||||||
|
# Find subtitle for renaming
|
||||||
|
fireEvent('renamer.before', group)
|
||||||
|
|
||||||
|
# Remove weird chars from moviename
|
||||||
|
movie_name = re.sub(r"[\x00\/\\:\*\?\"<>\|]", '', group['library']['titles'][0]['title'])
|
||||||
|
|
||||||
|
# Put 'The' at the end
|
||||||
|
name_the = movie_name
|
||||||
|
if movie_name[:4].lower() == 'the ':
|
||||||
|
name_the = movie_name[4:] + ', The'
|
||||||
|
|
||||||
|
replacements = {
|
||||||
|
'ext': 'mkv',
|
||||||
|
'namethe': name_the.strip(),
|
||||||
|
'thename': movie_name.strip(),
|
||||||
|
'year': library['year'],
|
||||||
|
'first': name_the[0].upper(),
|
||||||
|
'dirname': group['dirname'],
|
||||||
|
'quality': group['meta_data']['quality']['label'],
|
||||||
|
'quality_type': group['meta_data']['quality_type'],
|
||||||
|
'video': group['meta_data'].get('video'),
|
||||||
|
'audio': group['meta_data'].get('audio'),
|
||||||
|
'group': group['meta_data']['group'],
|
||||||
|
'source': group['meta_data']['source'],
|
||||||
|
'resolution_width': group['meta_data'].get('resolution_width'),
|
||||||
|
'resolution_height': group['meta_data'].get('resolution_height'),
|
||||||
|
}
|
||||||
|
|
||||||
|
for file_type in group['files']:
|
||||||
|
|
||||||
|
# Move DVD files (no renaming)
|
||||||
|
if group['is_dvd'] and file_type is 'movie':
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Move nfo depending on settings
|
||||||
|
if file_type is 'nfo' and not self.conf('rename_nfo'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Subtitle extra
|
||||||
|
if file_type is 'subtitle_extra':
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Move other files
|
||||||
|
multiple = len(group['files']['movie']) > 1
|
||||||
|
cd = 1 if multiple else 0
|
||||||
|
|
||||||
|
for file in sorted(list(group['files'][file_type])):
|
||||||
|
|
||||||
|
# Original filename
|
||||||
|
replacements['original'] = os.path.basename(file)
|
||||||
|
|
||||||
|
# Extension
|
||||||
|
replacements['ext'] = getExt(file)
|
||||||
|
|
||||||
|
# cd #
|
||||||
|
replacements['cd'] = ' cd%d' % cd if cd else ''
|
||||||
|
replacements['cd_nr'] = cd
|
||||||
|
|
||||||
|
# Naming
|
||||||
|
final_folder_name = self.doReplace(folder_name, replacements)
|
||||||
|
final_file_name = self.doReplace(file_name, replacements)
|
||||||
|
replacements['filename'] = final_file_name[:-(len(getExt(final_file_name)) + 1)]
|
||||||
|
|
||||||
|
# Meta naming
|
||||||
|
if file_type is 'trailer':
|
||||||
|
final_file_name = self.doReplace(trailer_name, replacements)
|
||||||
|
elif file_type is 'nfo':
|
||||||
|
final_file_name = self.doReplace(nfo_name, replacements)
|
||||||
|
elif file_type is 'backdrop':
|
||||||
|
final_file_name = self.doReplace(backdrop_name, replacements)
|
||||||
|
|
||||||
|
# Seperator replace
|
||||||
|
if separator:
|
||||||
|
final_file_name = final_file_name.replace(' ', separator)
|
||||||
|
|
||||||
|
# Main file
|
||||||
|
rename_files[file] = os.path.join(destination, final_folder_name, final_file_name)
|
||||||
|
|
||||||
|
# Check for extra subtitle files
|
||||||
|
if file_type is 'subtitle':
|
||||||
|
|
||||||
|
def test(s):
|
||||||
|
return file[:-len(replacements['ext'])] in s
|
||||||
|
|
||||||
|
for subtitle_extra in set(filter(test, group['files']['subtitle_extra'])):
|
||||||
|
replacements['ext'] = getExt(subtitle_extra)
|
||||||
|
|
||||||
|
final_folder_name = self.doReplace(folder_name, replacements)
|
||||||
|
final_file_name = self.doReplace(file_name, replacements)
|
||||||
|
rename_files[subtitle_extra] = os.path.join(destination, final_folder_name, final_file_name)
|
||||||
|
|
||||||
|
if multiple:
|
||||||
|
cd += 1
|
||||||
|
|
||||||
|
# Before renaming, remove the lower quality files
|
||||||
|
db = get_session()
|
||||||
|
library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
|
||||||
|
for movie in library.movies:
|
||||||
|
for release in movie.releases:
|
||||||
|
if release.quality.order < group['meta_data']['quality']['order']:
|
||||||
|
log.info('Removing older release for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
|
||||||
|
elif release.quality.order is group['meta_data']['quality']['order']:
|
||||||
|
log.info('Same quality release already exists for %s, with quality %s. Assuming repack.' % (movie.library.titles[0].title, release.quality.label))
|
||||||
|
else:
|
||||||
|
log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
|
||||||
|
|
||||||
|
# Add _EXISTS_ to the parent dir
|
||||||
|
if group['dirname']:
|
||||||
|
for rename_me in rename_files: # Don't rename anything in this group
|
||||||
|
rename_files[rename_me] = None
|
||||||
|
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_EXISTS_%s' % group['dirname'])
|
||||||
|
else: # Add it to filename
|
||||||
|
for rename_me in rename_files:
|
||||||
|
filename = os.path.basename(rename_me)
|
||||||
|
rename_files[rename_me] = rename_me.replace(filename, '_EXISTS_%s' % filename)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
for file in release.files:
|
||||||
|
log.info('Removing "%s"' % file.path)
|
||||||
|
|
||||||
|
# Rename
|
||||||
|
for rename_me in rename_files:
|
||||||
|
if rename_files[rename_me]:
|
||||||
|
log.info('Renaming "%s" to "%s"' % (rename_me, rename_files[rename_me]))
|
||||||
|
|
||||||
|
path = os.path.dirname(rename_files[rename_me])
|
||||||
|
try:
|
||||||
|
if not os.path.isdir(path): os.makedirs(path)
|
||||||
|
except:
|
||||||
|
log.error('Failed creating dir %s: %s' % (path, traceback.format_exc()))
|
||||||
|
continue
|
||||||
|
|
||||||
|
#print rename_me, rename_files[rename_me]
|
||||||
|
|
||||||
|
# Search for trailers
|
||||||
|
fireEvent('renamer.after', group)
|
||||||
|
|
||||||
|
def moveFile(self, old, dest, suppress = True):
|
||||||
|
try:
|
||||||
|
shutil.move(old, dest)
|
||||||
|
except:
|
||||||
|
log.error("Couldn't move file '%s' to '%s': %s" % (old, dest, traceback.format_exc()))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def doReplace(self, string, replacements):
|
||||||
|
'''
|
||||||
|
replace confignames with the real thing
|
||||||
|
'''
|
||||||
|
|
||||||
|
replaced = toUnicode(string)
|
||||||
|
for x, r in replacements.iteritems():
|
||||||
|
if r is not None:
|
||||||
|
replaced = replaced.replace('<%s>' % toUnicode(x), toUnicode(r))
|
||||||
|
else:
|
||||||
|
#If information is not available, we don't want the tag in the filename
|
||||||
|
replaced = replaced.replace('<' + x + '>', '')
|
||||||
|
|
||||||
|
replaced = re.sub(r"[\x00:\*\?\"<>\|]", '', replaced)
|
||||||
|
|
||||||
|
sep = self.conf('separator')
|
||||||
|
return self.replaceDoubles(replaced).replace(' ', ' ' if not sep else sep)
|
||||||
|
|
||||||
|
def replaceDoubles(self, string):
|
||||||
|
return string.replace(' ', ' ').replace(' .', '.')
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
|
||||||
from couchpotato.core.helpers.variable import getExt
|
from couchpotato.core.helpers.variable import getExt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import File, Library, Release, Movie
|
from couchpotato.core.settings.model import File, Release, Movie
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from flask.helpers import json
|
from flask.helpers import json
|
||||||
from themoviedb.tmdb import opensubtitleHashFile
|
from themoviedb.tmdb import opensubtitleHashFile
|
||||||
@@ -23,7 +23,7 @@ class Scanner(Plugin):
|
|||||||
'trailer': 1048576, # 1MB
|
'trailer': 1048576, # 1MB
|
||||||
}
|
}
|
||||||
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
|
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
|
||||||
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films']
|
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads']
|
||||||
extensions = {
|
extensions = {
|
||||||
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img'],
|
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img'],
|
||||||
'dvd': ['vts_*', 'vob'],
|
'dvd': ['vts_*', 'vob'],
|
||||||
@@ -42,7 +42,7 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
codecs = {
|
codecs = {
|
||||||
'audio': ['dts', 'ac3', 'ac3d', 'mp3'],
|
'audio': ['dts', 'ac3', 'ac3d', 'mp3'],
|
||||||
'video': ['x264', 'divx', 'xvid']
|
'video': ['x264', 'h264', 'divx', 'xvid']
|
||||||
}
|
}
|
||||||
|
|
||||||
source_media = {
|
source_media = {
|
||||||
@@ -52,12 +52,16 @@ class Scanner(Plugin):
|
|||||||
'hdtv': ['hdtv']
|
'hdtv': ['hdtv']
|
||||||
}
|
}
|
||||||
|
|
||||||
clean = '(?i)[^\s](ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])[^\s]*'
|
clean = '[ _\,\.\(\)\[\]\-](french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
|
||||||
multipart_regex = [
|
multipart_regex = [
|
||||||
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
||||||
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
||||||
'[ _\.-]+part[ _\.-]*([0-9a-d]+)', #*part1.mkv
|
'[ _\.-]+part[ _\.-]*([0-9a-d]+)', #*part1
|
||||||
'[ _\.-]+dis[ck][ _\.-]*([0-9a-d]+)', #*disk1.mkv
|
'[ _\.-]+dis[ck][ _\.-]*([0-9a-d]+)', #*disk1
|
||||||
|
'cd[ _\.-]*([0-9a-d]+)$', #cd1.ext
|
||||||
|
'dvd[ _\.-]*([0-9a-d]+)$', #dvd1.ext
|
||||||
|
'part[ _\.-]*([0-9a-d]+)$', #part1.mkv
|
||||||
|
'dis[ck][ _\.-]*([0-9a-d]+)$', #disk1.mkv
|
||||||
'()[ _\.-]+([0-9]*[abcd]+)(\.....?)$',
|
'()[ _\.-]+([0-9]*[abcd]+)(\.....?)$',
|
||||||
'([a-z])([0-9]+)(\.....?)$',
|
'([a-z])([0-9]+)(\.....?)$',
|
||||||
'()([ab])(\.....?)$' #*a.mkv
|
'()([ab])(\.....?)$' #*a.mkv
|
||||||
@@ -65,42 +69,54 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
addEvent('app.load', self.scan)
|
#addEvent('app.load', self.scanLibrary)
|
||||||
|
|
||||||
def scan(self, folder = '/Volumes/Media/Test/'):
|
addEvent('scanner.scan', self.scan)
|
||||||
|
|
||||||
"""
|
def scanLibrary(self):
|
||||||
Get all files
|
|
||||||
|
|
||||||
For each file larger then 350MB
|
folder = '/Volumes/Media/Test/'
|
||||||
create movie "group", this is where all movie files will be grouped
|
|
||||||
group multipart together
|
|
||||||
check if its DVD (VIDEO_TS)
|
|
||||||
|
|
||||||
# This should work for non-folder based structure
|
groups = self.scan(folder = folder)
|
||||||
for each moviegroup
|
|
||||||
|
|
||||||
for each file smaller then 350MB, allfiles.filter(moviename*)
|
# Open up the db
|
||||||
|
db = get_session()
|
||||||
|
|
||||||
# Assuming the beginning of the filename is the same for this structure
|
# Mark all files as "offline" before a adding them to the database (again)
|
||||||
Movie is masterfile, moviename-cd1.ext -> moviename
|
files_in_path = db.query(File).filter(File.path.like(toUnicode(folder) + u'%%'))
|
||||||
Find other files connected to moviename, moviename*.nfo, moviename*.sub, moviename*trailer.ext
|
files_in_path.update({'available': 0}, synchronize_session = False)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
Remove found file from allfiles
|
update_after = []
|
||||||
|
for group in groups.itervalues():
|
||||||
|
|
||||||
# This should work for folder based structure
|
# Save to DB
|
||||||
for each leftover file
|
if group['library']:
|
||||||
Loop over leftover files, use dirname as moviename
|
#library = db.query(Library).filter_by(id = library.get('id')).one()
|
||||||
|
|
||||||
|
# Add release
|
||||||
|
self.addRelease(group)
|
||||||
|
|
||||||
|
# Add identifier for library update
|
||||||
|
update_after.append(group['library'].get('identifier'))
|
||||||
|
|
||||||
|
for identifier in update_after:
|
||||||
|
fireEvent('library.update', identifier = identifier)
|
||||||
|
|
||||||
|
# If cleanup option is enabled, remove offline files from database
|
||||||
|
if self.conf('cleanup_offline'):
|
||||||
|
files_in_path = db.query(File).filter(File.path.like(folder + '%%')).filter_by(available = 0)
|
||||||
|
[db.delete(x) for x in files_in_path]
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
db.remove()
|
||||||
|
|
||||||
|
|
||||||
For each found movie
|
def scan(self, folder = None):
|
||||||
|
|
||||||
determine filetype
|
if not folder or not os.path.isdir(folder):
|
||||||
|
log.error('Folder doesn\'t exists: %s' % folder)
|
||||||
Check if it's already in the db
|
return {}
|
||||||
|
|
||||||
Add it to database
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Get movie "master" files
|
# Get movie "master" files
|
||||||
movie_files = {}
|
movie_files = {}
|
||||||
@@ -153,22 +169,16 @@ class Scanner(Plugin):
|
|||||||
# Remove the found files from the leftover stack
|
# Remove the found files from the leftover stack
|
||||||
leftovers = leftovers - found_files
|
leftovers = leftovers - found_files
|
||||||
|
|
||||||
# Open up the db
|
|
||||||
db = get_session()
|
|
||||||
|
|
||||||
# Mark all files as "offline" before a adding them to the database (again)
|
|
||||||
files_in_path = db.query(File).filter(File.path.like(toUnicode(folder) + u'%%'))
|
|
||||||
files_in_path.update({'available': 0}, synchronize_session = False)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
# Determine file types
|
# Determine file types
|
||||||
update_after = []
|
for identifier in movie_files:
|
||||||
for identifier, group in movie_files.iteritems():
|
group = movie_files[identifier]
|
||||||
|
|
||||||
# Group extra (and easy) files first
|
# Group extra (and easy) files first
|
||||||
images = self.getImages(group['unsorted_files'])
|
images = self.getImages(group['unsorted_files'])
|
||||||
group['files'] = {
|
group['files'] = {
|
||||||
'subtitle': self.getSubtitles(group['unsorted_files']),
|
'subtitle': self.getSubtitles(group['unsorted_files']),
|
||||||
|
'subtitle_extra': self.getSubtitlesExtras(group['unsorted_files']),
|
||||||
'nfo': self.getNfo(group['unsorted_files']),
|
'nfo': self.getNfo(group['unsorted_files']),
|
||||||
'trailer': self.getTrailers(group['unsorted_files']),
|
'trailer': self.getTrailers(group['unsorted_files']),
|
||||||
'backdrop': images['backdrop'],
|
'backdrop': images['backdrop'],
|
||||||
@@ -180,7 +190,22 @@ class Scanner(Plugin):
|
|||||||
group['files']['movie'] = self.getDVDFiles(group['unsorted_files'])
|
group['files']['movie'] = self.getDVDFiles(group['unsorted_files'])
|
||||||
else:
|
else:
|
||||||
group['files']['movie'] = self.getMediaFiles(group['unsorted_files'])
|
group['files']['movie'] = self.getMediaFiles(group['unsorted_files'])
|
||||||
group['meta_data'] = self.getMetaData(group['files']['movie'])
|
group['meta_data'] = self.getMetaData(group)
|
||||||
|
|
||||||
|
# Get parent dir from movie files
|
||||||
|
for movie_file in group['files']['movie']:
|
||||||
|
group['parentdir'] = os.path.dirname(movie_file)
|
||||||
|
group['dirname'] = None
|
||||||
|
|
||||||
|
folders = group['parentdir'].replace(folder, '').split(os.path.sep)
|
||||||
|
|
||||||
|
# Try and get a proper dirname, so no "A", "Movie", "Download"
|
||||||
|
for folder in folders:
|
||||||
|
if folder.lower() in self.ignore_names or len(folder) < 2:
|
||||||
|
group['dirname'] = folder
|
||||||
|
break
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
# Leftover "sorted" files
|
# Leftover "sorted" files
|
||||||
for type in group['files']:
|
for type in group['files']:
|
||||||
@@ -191,34 +216,16 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
# Determine movie
|
# Determine movie
|
||||||
group['library'] = self.determineMovie(group)
|
group['library'] = self.determineMovie(group)
|
||||||
|
if not group['library']:
|
||||||
|
log.error('Unable to determin movie: %s' % group['identifiers'])
|
||||||
|
|
||||||
# Save to DB
|
return movie_files
|
||||||
if group['library']:
|
|
||||||
#library = db.query(Library).filter_by(id = library.get('id')).one()
|
|
||||||
|
|
||||||
# Add release
|
|
||||||
release = self.addRelease(group)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Add identifier for library update
|
|
||||||
update_after.append(group['library'].get('identifier'))
|
|
||||||
|
|
||||||
for identifier in update_after:
|
|
||||||
fireEvent('library.update', identifier = identifier)
|
|
||||||
|
|
||||||
# If cleanup option is enabled, remove offline files from database
|
|
||||||
if self.conf('cleanup_offline'):
|
|
||||||
files_in_path = db.query(File).filter(File.path.like(folder + '%%')).filter_by(available = 0)
|
|
||||||
[db.delete(x) for x in files_in_path]
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
db.remove()
|
|
||||||
|
|
||||||
|
|
||||||
def addRelease(self, group):
|
def addRelease(self, group):
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data']['audio'], group['meta_data']['quality'])
|
identifier = '%s.%s.%s' % (group['library']['identifier'], group['meta_data'].get('audio', 'unknown'), group['meta_data']['quality']['identifier'])
|
||||||
|
|
||||||
# Add movie
|
# Add movie
|
||||||
done_status = fireEvent('status.get', 'done', single = True)
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
@@ -233,13 +240,13 @@ class Scanner(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# Add release
|
# Add release
|
||||||
quality = fireEvent('quality.single', group['meta_data']['quality'], single = True)
|
|
||||||
release = db.query(Release).filter_by(identifier = identifier).first()
|
release = db.query(Release).filter_by(identifier = identifier).first()
|
||||||
if not release:
|
if not release:
|
||||||
|
|
||||||
release = Release(
|
release = Release(
|
||||||
identifier = identifier,
|
identifier = identifier,
|
||||||
movie = movie,
|
movie = movie,
|
||||||
quality_id = quality.get('id'),
|
quality_id = group['meta_data']['quality'].get('id'),
|
||||||
status_id = done_status.get('id')
|
status_id = done_status.get('id')
|
||||||
)
|
)
|
||||||
db.add(release)
|
db.add(release)
|
||||||
@@ -259,18 +266,36 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
db.remove()
|
db.remove()
|
||||||
|
|
||||||
def getMetaData(self, files):
|
def getMetaData(self, group):
|
||||||
|
|
||||||
return {
|
data = {}
|
||||||
'audio': 'AC3',
|
files = group['files']['movie']
|
||||||
'quality': '720p',
|
|
||||||
'quality_type': 'HD',
|
|
||||||
'resolution_width': 1280,
|
|
||||||
'resolution_height': 720
|
|
||||||
}
|
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
self.getMeta(file)
|
if os.path.getsize(file) < self.minimal_filesize['media']: continue # Ignore smaller files
|
||||||
|
|
||||||
|
meta = self.getMeta(file)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data['video'] = self.getCodec(file, self.codecs['video'])
|
||||||
|
data['audio'] = meta['audio stream'][0]['compression']
|
||||||
|
data['resolution_width'] = meta['video stream'][0]['image width']
|
||||||
|
data['resolution_height'] = meta['video stream'][0]['image height']
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if data.get('audio'): break
|
||||||
|
|
||||||
|
data['quality'] = fireEvent('quality.guess', files = files, extra = data, single = True)
|
||||||
|
if not data['quality']:
|
||||||
|
data['quality'] = fireEvent('quality.single', 'dvdr' if group['is_dvd'] else 'dvdrip', single = True)
|
||||||
|
|
||||||
|
data['quality_type'] = 'HD' if data.get('resolution_width', 0) >= 720 else 'SD'
|
||||||
|
|
||||||
|
data['group'] = self.getGroup(file[0])
|
||||||
|
data['source'] = self.getSourceMedia(file[0])
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
def getMeta(self, filename):
|
def getMeta(self, filename):
|
||||||
lib_dir = os.path.join(Env.get('app_dir'), 'libs')
|
lib_dir = os.path.join(Env.get('app_dir'), 'libs')
|
||||||
@@ -281,40 +306,58 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
meta = json.loads(z)
|
meta = json.loads(z)
|
||||||
log.info('Retrieved metainfo: %s' % meta)
|
|
||||||
return meta
|
return meta
|
||||||
except Exception, e:
|
except Exception:
|
||||||
print e
|
log.error('Couldn\'t get metadata from file: %s' % traceback.format_exc())
|
||||||
log.error('Couldn\'t get metadata from file')
|
|
||||||
|
|
||||||
def determineMovie(self, group):
|
def determineMovie(self, group):
|
||||||
imdb_id = None
|
imdb_id = None
|
||||||
|
|
||||||
files = group['files']
|
files = group['files']
|
||||||
# Check and see if nfo contains the imdb-id
|
|
||||||
try:
|
|
||||||
for nfo_file in files['nfo']:
|
|
||||||
imdb_id = self.getImdb(nfo_file)
|
|
||||||
if imdb_id: break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Check if path is already in db
|
# Check for CP(imdb_id) string in the file paths
|
||||||
db = get_session()
|
|
||||||
for file in files['movie']:
|
for file in files['movie']:
|
||||||
f = db.query(File).filter_by(path = toUnicode(file)).first()
|
imdb_id = self.getCPImdb(file)
|
||||||
|
if imdb_id: break
|
||||||
|
|
||||||
|
# Check and see if nfo contains the imdb-id
|
||||||
|
if not imdb_id:
|
||||||
try:
|
try:
|
||||||
imdb_id = f.library[0].identifier
|
for nfo_file in files['nfo']:
|
||||||
break
|
imdb_id = self.getImdb(nfo_file)
|
||||||
|
if imdb_id: break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
db.remove()
|
|
||||||
|
# Check if path is already in db
|
||||||
|
if not imdb_id:
|
||||||
|
db = get_session()
|
||||||
|
for file in files['movie']:
|
||||||
|
f = db.query(File).filter_by(path = toUnicode(file)).first()
|
||||||
|
try:
|
||||||
|
imdb_id = f.library[0].identifier
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
db.remove()
|
||||||
|
|
||||||
|
# Search based on OpenSubtitleHash
|
||||||
|
if not imdb_id and not group['is_dvd']:
|
||||||
|
for file in files['movie']:
|
||||||
|
movie = fireEvent('provider.movie.by_hash', file = file, merge = True)
|
||||||
|
|
||||||
|
if len(movie) > 0:
|
||||||
|
imdb_id = movie[0]['imdb']
|
||||||
|
if imdb_id: break
|
||||||
|
|
||||||
# Search based on identifiers
|
# Search based on identifiers
|
||||||
if not imdb_id:
|
if not imdb_id:
|
||||||
for identifier in group['identifiers']:
|
for identifier in group['identifiers']:
|
||||||
|
|
||||||
if len(identifier) > 2:
|
if len(identifier) > 2:
|
||||||
|
|
||||||
movie = fireEvent('provider.movie.search', q = identifier, merge = True, limit = 1)
|
movie = fireEvent('provider.movie.search', q = identifier, merge = True, limit = 1)
|
||||||
|
|
||||||
if len(movie) > 0:
|
if len(movie) > 0:
|
||||||
imdb_id = movie[0]['imdb']
|
imdb_id = movie[0]['imdb']
|
||||||
if imdb_id: break
|
if imdb_id: break
|
||||||
@@ -329,7 +372,7 @@ class Scanner(Plugin):
|
|||||||
}, update_after = False, single = True)
|
}, update_after = False, single = True)
|
||||||
|
|
||||||
log.error('No imdb_id found for %s.' % group['identifiers'])
|
log.error('No imdb_id found for %s.' % group['identifiers'])
|
||||||
return False
|
return {}
|
||||||
|
|
||||||
def saveFile(self, file, type = 'unknown', include_media_info = False):
|
def saveFile(self, file, type = 'unknown', include_media_info = False):
|
||||||
|
|
||||||
@@ -342,6 +385,17 @@ class Scanner(Plugin):
|
|||||||
# Check database and update/insert if necessary
|
# Check database and update/insert if necessary
|
||||||
return fireEvent('file.add', path = file, part = self.getPartNumber(file), type = self.file_types[type], properties = properties, single = True)
|
return fireEvent('file.add', path = file, part = self.getPartNumber(file), type = self.file_types[type], properties = properties, single = True)
|
||||||
|
|
||||||
|
def getCPImdb(self, string):
|
||||||
|
|
||||||
|
try:
|
||||||
|
m = re.search('(cp\((?P<id>tt[0-9{7}]+)\))', string.lower())
|
||||||
|
id = m.group('id')
|
||||||
|
if id: return id
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def getImdb(self, txt):
|
def getImdb(self, txt):
|
||||||
|
|
||||||
if os.path.isfile(txt):
|
if os.path.isfile(txt):
|
||||||
@@ -375,6 +429,9 @@ class Scanner(Plugin):
|
|||||||
def getSubtitles(self, files):
|
def getSubtitles(self, files):
|
||||||
return set(filter(lambda s: getExt(s.lower()) in self.extensions['subtitle'], files))
|
return set(filter(lambda s: getExt(s.lower()) in self.extensions['subtitle'], files))
|
||||||
|
|
||||||
|
def getSubtitlesExtras(self, files):
|
||||||
|
return set(filter(lambda s: getExt(s.lower()) in self.extensions['subtitle_extra'], files))
|
||||||
|
|
||||||
def getNfo(self, files):
|
def getNfo(self, files):
|
||||||
return set(filter(lambda s: getExt(s.lower()) in self.extensions['nfo'], files))
|
return set(filter(lambda s: getExt(s.lower()) in self.extensions['nfo'], files))
|
||||||
|
|
||||||
@@ -447,20 +504,42 @@ class Scanner(Plugin):
|
|||||||
return set(filter(lambda s:identifier in self.createFileIdentifier(s, folder), file_pile))
|
return set(filter(lambda s:identifier in self.createFileIdentifier(s, folder), file_pile))
|
||||||
|
|
||||||
def createFileIdentifier(self, file_path, folder, exclude_filename = False):
|
def createFileIdentifier(self, file_path, folder, exclude_filename = False):
|
||||||
|
|
||||||
identifier = file_path.replace(folder, '') # root folder
|
identifier = file_path.replace(folder, '') # root folder
|
||||||
identifier = os.path.splitext(identifier)[0] # ext
|
identifier = os.path.splitext(identifier)[0] # ext
|
||||||
|
|
||||||
if exclude_filename:
|
if exclude_filename:
|
||||||
identifier = identifier[:len(identifier) - len(os.path.split(identifier)[-1])]
|
identifier = identifier[:len(identifier) - len(os.path.split(identifier)[-1])]
|
||||||
identifier = self.removeMultipart(identifier) # multipart
|
|
||||||
|
|
||||||
return identifier
|
# multipart
|
||||||
|
identifier = self.removeMultipart(identifier)
|
||||||
|
|
||||||
|
# groups, release tags, scenename cleaner, regex isn't correct
|
||||||
|
identifier = re.sub(self.clean, '::', simplifyString(identifier))
|
||||||
|
|
||||||
|
year = self.findYear(identifier)
|
||||||
|
if year:
|
||||||
|
identifier = '%s %s' % (identifier.split(year)[0].strip(), year)
|
||||||
|
else:
|
||||||
|
identifier = identifier.split('::')[0]
|
||||||
|
|
||||||
|
# Remove duplicates
|
||||||
|
out = []
|
||||||
|
for word in identifier.split():
|
||||||
|
if not word in out:
|
||||||
|
out.append(word)
|
||||||
|
|
||||||
|
identifier = ' '.join(out)
|
||||||
|
|
||||||
|
return simplifyString(identifier)
|
||||||
|
|
||||||
|
|
||||||
def removeMultipart(self, name):
|
def removeMultipart(self, name):
|
||||||
for regex in self.multipart_regex:
|
for regex in self.multipart_regex:
|
||||||
try:
|
try:
|
||||||
found = re.sub(regex, '', name)
|
found = re.sub(regex, '', name)
|
||||||
if found != name:
|
if found != name:
|
||||||
return found
|
name = found
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return name
|
return name
|
||||||
@@ -475,3 +554,33 @@ class Scanner(Plugin):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
def getCodec(self, filename, codecs):
|
||||||
|
codecs = map(re.escape, codecs)
|
||||||
|
try:
|
||||||
|
codec = re.search('[^A-Z0-9](?P<codec>' + '|'.join(codecs) + ')[^A-Z0-9]', filename, re.I)
|
||||||
|
return (codec and codec.group('codec')) or ''
|
||||||
|
except:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def getGroup(self, file):
|
||||||
|
try:
|
||||||
|
group = re.search('-(?P<group>[A-Z0-9]+)$', file, re.I)
|
||||||
|
return group.group('group') or ''
|
||||||
|
except:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def getSourceMedia(self, file):
|
||||||
|
for media in self.source_media:
|
||||||
|
for alias in self.source_media[media]:
|
||||||
|
if alias in file.lower():
|
||||||
|
return media
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def findYear(self, text):
|
||||||
|
matches = re.search('(?P<year>[0-9]{4})', text)
|
||||||
|
if matches:
|
||||||
|
return matches.group('year')
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
# Schedule cronjob
|
# Schedule cronjob
|
||||||
fireEvent('schedule.cron', 'searcher.all', self.all, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
fireEvent('schedule.cron', 'searcher.all', self.all, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
||||||
addEvent('app.load', self.all)
|
#addEvent('app.load', self.all)
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,44 @@ class TheMovieDb(MovieProvider):
|
|||||||
imageUrl = 'http://hwcdn.themoviedb.org'
|
imageUrl = 'http://hwcdn.themoviedb.org'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
addEvent('provider.movie.by_hash', self.byHash)
|
||||||
addEvent('provider.movie.search', self.search)
|
addEvent('provider.movie.search', self.search)
|
||||||
addEvent('provider.movie.info', self.getInfo)
|
addEvent('provider.movie.info', self.getInfo)
|
||||||
|
|
||||||
# Use base wrapper
|
# Use base wrapper
|
||||||
tmdb.Config.api_key = self.conf('api_key')
|
tmdb.Config.api_key = self.conf('api_key')
|
||||||
|
|
||||||
|
def byHash(self, file):
|
||||||
|
''' Find movie by hash '''
|
||||||
|
|
||||||
|
if self.isDisabled():
|
||||||
|
return False
|
||||||
|
|
||||||
|
cache_key = 'tmdb.cache.%s' % simplifyString(file)
|
||||||
|
results = self.getCache(cache_key)
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
log.debug('Searching for movie by hash: %s' % file)
|
||||||
|
try:
|
||||||
|
raw = tmdb.searchByHashingFile(file)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
if raw:
|
||||||
|
try:
|
||||||
|
results = self.parseMovie(raw)
|
||||||
|
log.info('Found: %s' % results['titles'][0] + ' (' + str(results['year']) + ')')
|
||||||
|
|
||||||
|
self.setCache(cache_key, results)
|
||||||
|
return results
|
||||||
|
except SyntaxError, e:
|
||||||
|
log.error('Failed to parse XML response: %s' % e)
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
log.debug('No movies known by hash for: %s' % file)
|
||||||
|
pass
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
def search(self, q, limit = 12):
|
def search(self, q, limit = 12):
|
||||||
''' Find movie by name '''
|
''' Find movie by name '''
|
||||||
|
|
||||||
@@ -32,7 +64,7 @@ class TheMovieDb(MovieProvider):
|
|||||||
results = self.getCache(cache_key)
|
results = self.getCache(cache_key)
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
log.debug('TheMovieDB - Searching for movie: %s' % q)
|
log.debug('Searching for movie: %s' % q)
|
||||||
raw = tmdb.search(search_string)
|
raw = tmdb.search(search_string)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
@@ -47,7 +79,8 @@ class TheMovieDb(MovieProvider):
|
|||||||
if nr == limit:
|
if nr == limit:
|
||||||
break
|
break
|
||||||
|
|
||||||
log.info('TheMovieDB - Found: %s' % [result['titles'][0] + ' (' + str(result['year']) + ')' for result in results])
|
log.info('Found: %s' % [result['titles'][0] + ' (' + str(result['year']) + ')' for result in results])
|
||||||
|
|
||||||
self.setCache(cache_key, results)
|
self.setCache(cache_key, results)
|
||||||
return results
|
return results
|
||||||
except SyntaxError, e:
|
except SyntaxError, e:
|
||||||
|
|||||||
@@ -42,13 +42,14 @@ class Library(Entity):
|
|||||||
tagline = Field(UnicodeText(255))
|
tagline = Field(UnicodeText(255))
|
||||||
|
|
||||||
status = ManyToOne('Status')
|
status = ManyToOne('Status')
|
||||||
movie = OneToMany('Movie')
|
movies = OneToMany('Movie')
|
||||||
titles = OneToMany('LibraryTitle', order_by = '-default')
|
titles = OneToMany('LibraryTitle')
|
||||||
files = ManyToMany('File')
|
files = ManyToMany('File')
|
||||||
|
|
||||||
|
|
||||||
class LibraryTitle(Entity):
|
class LibraryTitle(Entity):
|
||||||
""""""
|
""""""
|
||||||
|
using_options(order_by = '-default')
|
||||||
|
|
||||||
title = Field(Unicode)
|
title = Field(Unicode)
|
||||||
default = Field(Boolean)
|
default = Field(Boolean)
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
---
|
||||||
|
name: Form.CheckGroup
|
||||||
|
description: Class to represent a group of Form.Check wrapped checkboxes
|
||||||
|
authors: Bryan J Swift (@bryanjswift)
|
||||||
|
license: MIT-style license.
|
||||||
|
requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Form-Replacement/Form.Check]
|
||||||
|
provides: Form.CheckGroup
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
if (typeof window.Form === 'undefined') { window.Form = {}; }
|
||||||
|
|
||||||
|
Form.CheckGroup = new Class({
|
||||||
|
Implements: [Events,Options],
|
||||||
|
options: {
|
||||||
|
checkOptions: {},
|
||||||
|
initialValues: {}
|
||||||
|
},
|
||||||
|
checks: [],
|
||||||
|
initialize: function(group,options) {
|
||||||
|
if (!Form.Check) { throw 'required Class Form.Check not found'; }
|
||||||
|
this.setOptions(options);
|
||||||
|
group = $(group);
|
||||||
|
if (!group) { return this; }
|
||||||
|
var checkboxes = group.getElements('input[type=checkbox]');
|
||||||
|
checkboxes.each(this.addCheck,this);
|
||||||
|
},
|
||||||
|
addCheck: function(checkbox) {
|
||||||
|
var initialValues = this.options.initialValues[checkbox.get('name')];
|
||||||
|
var checkOptions = {};
|
||||||
|
checkOptions.checked = initialValues ? initialValues.contains(checkbox.get('value')) : checkbox.get('checked');
|
||||||
|
checkOptions.disabled = checkbox.get('disabled');
|
||||||
|
checkbox.store('Form.CheckGroup::data',this);
|
||||||
|
var check = checkbox.retrieve('Form.Check::data') || new Form.Check(checkbox, Object.append(checkOptions,this.options.checkOptions));
|
||||||
|
this.checks.push(check);
|
||||||
|
},
|
||||||
|
checkAll: function() {
|
||||||
|
this.checks.each(function(check) { if (!check.checked) { check.toggle(); } });
|
||||||
|
},
|
||||||
|
disable: function() {
|
||||||
|
this.checks.each(function(check) { check.disable(); });
|
||||||
|
this.fireEvent('disable',this);
|
||||||
|
},
|
||||||
|
enable: function() {
|
||||||
|
this.checks.each(function(check) { check.enable(); });
|
||||||
|
this.fireEvent('enable',this);
|
||||||
|
},
|
||||||
|
uncheckAll: function() {
|
||||||
|
this.checks.each(function(check) { if (check.checked) { check.toggle(); } });
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
---
|
||||||
|
name: Form.RadioGroup
|
||||||
|
description: Class to represent a group of Form.Radio buttons
|
||||||
|
authors: Bryan J Swift (@bryanjswift)
|
||||||
|
license: MIT-style license.
|
||||||
|
requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Form-Replacement/Form.Radio]
|
||||||
|
provides: Form.RadioGroup
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
if (typeof window.Form === 'undefined') { window.Form = {}; }
|
||||||
|
|
||||||
|
Form.RadioGroup = new Class({
|
||||||
|
Implements: [Events,Options],
|
||||||
|
options: {
|
||||||
|
radioOptions: {},
|
||||||
|
initialValues: {}
|
||||||
|
},
|
||||||
|
bound: {},
|
||||||
|
radios: [],
|
||||||
|
value: null,
|
||||||
|
initialize: function(group,options) {
|
||||||
|
if (!Form.Radio) { throw 'required Class Form.Radio not found'; }
|
||||||
|
this.setOptions(options);
|
||||||
|
this.bound = { select: this.select.bind(this) };
|
||||||
|
group = $(group);
|
||||||
|
if (!group) { return this; }
|
||||||
|
var radios = group.getElements('input[type=radio]');
|
||||||
|
radios.each(this.addCheck,this);
|
||||||
|
},
|
||||||
|
addCheck: function(radio,i) {
|
||||||
|
var initialValues = this.options.initialValues[radio.get('name')];
|
||||||
|
var radioOptions = {};
|
||||||
|
radioOptions.checked = initialValues ? initialValues.contains(radio.get('value')) : radio.get('checked');
|
||||||
|
radioOptions.disabled = radio.get('disabled');
|
||||||
|
var check = (radio.retrieve('Form.Radio::data')
|
||||||
|
|| new Form.Radio(radio,Object.append(radioOptions,this.options.radioOptions)));
|
||||||
|
check.addEvent('onCheck',this.bound.select);
|
||||||
|
if (check.checked) { i ? this.changed(check) : this.value = check.value; }
|
||||||
|
radio.store('Form.RadioGroup::data',this);
|
||||||
|
this.radios.push(check);
|
||||||
|
},
|
||||||
|
changed: function(radio) {
|
||||||
|
this.value = radio.value;
|
||||||
|
this.fireEvent('onChange',this);
|
||||||
|
},
|
||||||
|
disable: function() {
|
||||||
|
this.radios.each(function(radio) { radio.disable(); });
|
||||||
|
},
|
||||||
|
enable: function() {
|
||||||
|
this.radios.each(function(radio) { radio.enable(); });
|
||||||
|
},
|
||||||
|
select: function(checkedRadio) {
|
||||||
|
this.radios.each(function(radio) {
|
||||||
|
if (radio.checked && radio.value !== checkedRadio.value) { radio.uncheck(); }
|
||||||
|
});
|
||||||
|
if (checkedRadio.value !== this.value) { this.changed(checkedRadio); }
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
---
|
||||||
|
name: Form.Check
|
||||||
|
description: Class to represent a checkbox
|
||||||
|
authors: Bryan J Swift (@bryanjswift)
|
||||||
|
license: MIT-style license.
|
||||||
|
requires: [Core/Class.Extras, Core/Element, Core/Element.Event]
|
||||||
|
provides: Form.Check
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
if (typeof window.Form === 'undefined') { window.Form = {}; }
|
||||||
|
|
||||||
|
Form.Check = new Class({
|
||||||
|
Implements: [Events, Options],
|
||||||
|
options: {
|
||||||
|
checked: false,
|
||||||
|
disabled: false
|
||||||
|
},
|
||||||
|
bound: {},
|
||||||
|
checked: false,
|
||||||
|
config: {
|
||||||
|
checkedClass: 'checked',
|
||||||
|
disabledClass: 'disabled',
|
||||||
|
elementClass: 'check',
|
||||||
|
highlightedClass: 'highlighted',
|
||||||
|
storage: 'Form.Check::data'
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
element: null,
|
||||||
|
input: null,
|
||||||
|
label: null,
|
||||||
|
value: null,
|
||||||
|
initialize: function(input, options) {
|
||||||
|
this.setOptions(options);
|
||||||
|
this.bound = {
|
||||||
|
disable: this.disable.bind(this),
|
||||||
|
enable: this.enable.bind(this),
|
||||||
|
highlight: this.highlight.bind(this),
|
||||||
|
removeHighlight: this.removeHighlight.bind(this),
|
||||||
|
keyToggle: this.keyToggle.bind(this),
|
||||||
|
toggle: this.toggle.bind(this)
|
||||||
|
};
|
||||||
|
var bound = this.bound;
|
||||||
|
input = this.input = $(input);
|
||||||
|
var id = input.get('id');
|
||||||
|
this.label = document.getElement('label[for=' + id + ']');
|
||||||
|
this.element = new Element('div', {
|
||||||
|
'class': input.get('class') + ' ' + this.config.elementClass,
|
||||||
|
id: id ? id + 'Check' : '',
|
||||||
|
events: {
|
||||||
|
click: bound.toggle,
|
||||||
|
mouseenter: bound.highlight,
|
||||||
|
mouseleave: bound.removeHighlight
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.input.addEvents({
|
||||||
|
keypress: bound.keyToggle,
|
||||||
|
keydown: bound.keyToggle,
|
||||||
|
keyup: bound.keyToggle
|
||||||
|
});
|
||||||
|
if (this.label) { this.label.addEvent('click', bound.toggle); }
|
||||||
|
this.element.wraps(input);
|
||||||
|
this.value = input.get('value');
|
||||||
|
if (this.input.checked) { this.check(); } else { this.uncheck(); }
|
||||||
|
if (this.input.disabled) { this.disable(); } else { this.enable(); }
|
||||||
|
input.store(this.config.storage, this).addEvents({
|
||||||
|
blur: bound.removeHighlight,
|
||||||
|
focus: bound.highlight
|
||||||
|
});
|
||||||
|
this.fireEvent('create', this);
|
||||||
|
},
|
||||||
|
check: function() {
|
||||||
|
this.element.addClass(this.config.checkedClass);
|
||||||
|
this.input.set('checked', 'checked').focus();
|
||||||
|
this.checked = true;
|
||||||
|
this.fireEvent('check', this);
|
||||||
|
},
|
||||||
|
disable: function() {
|
||||||
|
this.element.addClass(this.config.disabledClass);
|
||||||
|
this.input.set('disabled', 'disabled');
|
||||||
|
this.disabled = true;
|
||||||
|
this.fireEvent('disable', this);
|
||||||
|
},
|
||||||
|
enable: function() {
|
||||||
|
this.element.removeClass(this.config.disabledClass);
|
||||||
|
this.input.erase('disabled');
|
||||||
|
this.disabled = false;
|
||||||
|
this.fireEvent('enable', this);
|
||||||
|
},
|
||||||
|
highlight: function() {
|
||||||
|
this.element.addClass(this.config.highlightedClass);
|
||||||
|
this.fireEvent('highlight', this);
|
||||||
|
},
|
||||||
|
removeHighlight: function() {
|
||||||
|
this.element.removeClass(this.config.highlightedClass);
|
||||||
|
this.fireEvent('removeHighlight', this);
|
||||||
|
},
|
||||||
|
keyToggle: function(e) {
|
||||||
|
var evt = new Event(e);
|
||||||
|
if (evt.key === 'space') { this.toggle(e); }
|
||||||
|
},
|
||||||
|
toggle: function(e) {
|
||||||
|
var evt;
|
||||||
|
if (this.disabled) { return this; }
|
||||||
|
if (e) {
|
||||||
|
evt = new Event(e).stopPropagation();
|
||||||
|
if (evt.target.tagName.toLowerCase() !== 'a') {
|
||||||
|
evt.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.checked) {
|
||||||
|
this.uncheck();
|
||||||
|
} else {
|
||||||
|
this.check();
|
||||||
|
}
|
||||||
|
this.fireEvent('change', this);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
uncheck: function() {
|
||||||
|
this.element.removeClass(this.config.checkedClass);
|
||||||
|
this.input.erase('checked');
|
||||||
|
this.checked = false;
|
||||||
|
this.fireEvent('uncheck', this);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
/*
|
||||||
|
---
|
||||||
|
name: Form.Dropdown
|
||||||
|
description: Class to represent a select input
|
||||||
|
authors: Bryan J Swift (@bryanjswift)
|
||||||
|
license: MIT-style license.
|
||||||
|
requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Form-Replacement/Form.SelectOption]
|
||||||
|
provides: Form.Dropdown
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
if (typeof window.Form === 'undefined') { window.Form = {}; }
|
||||||
|
|
||||||
|
Form.Dropdown = new Class({
|
||||||
|
Implements: [Events,Options],
|
||||||
|
options: {
|
||||||
|
excludedValues: [],
|
||||||
|
initialValue: null,
|
||||||
|
mouseLeaveDelay: 350,
|
||||||
|
selectOptions: {},
|
||||||
|
typeDelay: 500
|
||||||
|
},
|
||||||
|
bound: {},
|
||||||
|
dropdownOptions: [],
|
||||||
|
element: null,
|
||||||
|
events: {},
|
||||||
|
highlighted: null,
|
||||||
|
input: null,
|
||||||
|
open: true,
|
||||||
|
selected: null,
|
||||||
|
selection: null,
|
||||||
|
typed: { lastKey: null, value: null, timer: null, pressed: null, shortlist: [], startkey: null },
|
||||||
|
value: null,
|
||||||
|
initialize: function(select,options) {
|
||||||
|
this.setOptions(options);
|
||||||
|
select = $(select);
|
||||||
|
this.bound = {
|
||||||
|
collapse: this.collapse.bind(this),
|
||||||
|
expand: this.expand.bind(this),
|
||||||
|
focus: this.focus.bind(this),
|
||||||
|
highlightOption: this.highlightOption.bind(this),
|
||||||
|
keydown: this.keydown.bind(this),
|
||||||
|
keypress: this.keypress.bind(this),
|
||||||
|
mouseenterDropdown: this.mouseenterDropdown.bind(this),
|
||||||
|
mouseleaveDropdown: this.mouseleaveDropdown.bind(this),
|
||||||
|
mousemove: this.mousemove.bind(this),
|
||||||
|
removeHighlightOption: this.removeHighlightOption.bind(this),
|
||||||
|
select: this.select.bind(this),
|
||||||
|
toggle: this.toggle.bind(this)
|
||||||
|
};
|
||||||
|
this.events = { mouseenter: this.bound.mouseenterDropdown, mouseleave: this.bound.mouseleaveDropdown };
|
||||||
|
this.value = this.options.initialValue;
|
||||||
|
this.initializeCreateElements(select);
|
||||||
|
var optionElements = select.getElements('option');
|
||||||
|
this.updateOptions(optionElements);
|
||||||
|
this.element.replaces(select);
|
||||||
|
document.addEvent('click', this.bound.collapse);
|
||||||
|
var eventName = Browser.ie || Browser.webkit ? 'keydown' : 'keypress';
|
||||||
|
var target = Browser.ie ? $(document.body) : window;
|
||||||
|
target.addEvent('keydown',this.bound.keydown).addEvent(eventName,this.bound.keypress);
|
||||||
|
},
|
||||||
|
initializeCreateElements: function(select) {
|
||||||
|
var id = select.get('id');
|
||||||
|
var dropdown = new Element('div', {
|
||||||
|
'class': (select.get('class') + ' select').trim(),
|
||||||
|
'id': (id && id !== '') ? id + 'Dropdown' : ''
|
||||||
|
});
|
||||||
|
var menu = new Element('div', {'class': 'menu'});
|
||||||
|
var list = new Element('div', {'class': 'list'});
|
||||||
|
var options = new Element('ul', {'class': 'options'});
|
||||||
|
dropdown.adopt(menu.adopt(list.adopt(options)));
|
||||||
|
var dropdownSelection = new Element('div', {
|
||||||
|
'class': 'selection',
|
||||||
|
events: {click: this.bound.toggle}
|
||||||
|
});
|
||||||
|
var dropdownBackground = new Element('div', { 'class': 'dropdownBackground' });
|
||||||
|
var selection = new Element('span', { 'class': 'selectionDisplay' });
|
||||||
|
var input = new Element('input', {
|
||||||
|
type:'text',
|
||||||
|
id: id,
|
||||||
|
name: select.get('name'),
|
||||||
|
events: {
|
||||||
|
focus: this.bound.focus
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dropdownSelection.adopt(dropdownBackground, selection, input);
|
||||||
|
dropdown.adopt(dropdownSelection);
|
||||||
|
this.element = dropdown;
|
||||||
|
this.selection = selection;
|
||||||
|
this.input = input;
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
collapse: function(e) {
|
||||||
|
this.open = false;
|
||||||
|
this.element.removeClass('active').removeClass('dropdown-active');
|
||||||
|
if (this.selected) { this.selected.removeHighlight(); }
|
||||||
|
this.element.removeEvents(this.events);
|
||||||
|
this.fireEvent('collapse', [this, e]);
|
||||||
|
},
|
||||||
|
deselect: function(option) {
|
||||||
|
option.deselect();
|
||||||
|
},
|
||||||
|
destroy: function() {
|
||||||
|
this.element = null;
|
||||||
|
this.selection = null;
|
||||||
|
this.input = null;
|
||||||
|
},
|
||||||
|
disable: function() {
|
||||||
|
this.collapse();
|
||||||
|
this.input.set('disabled', 'disabled').removeEvents({blur:this.bound.blur, focus:this.bound.focus});
|
||||||
|
this.selection.getParent().removeEvent('click', this.bound.toggle);
|
||||||
|
this.fireEvent('disable', this);
|
||||||
|
},
|
||||||
|
enable: function() {
|
||||||
|
this.input.erase('disabled').addEvents({blur:this.bound.blur, focus:this.bound.focus});
|
||||||
|
this.selection.getParent().addEvent('click', this.bound.toggle);
|
||||||
|
this.fireEvent('enable', this);
|
||||||
|
},
|
||||||
|
expand: function(e) {
|
||||||
|
clearTimeout(this.collapseInterval);
|
||||||
|
var evt = e ? new Event(e).stop() : null;
|
||||||
|
this.open = true;
|
||||||
|
this.input.focus();
|
||||||
|
this.element.addClass('active').addClass('dropdown-active');
|
||||||
|
if (this.selected) { this.selected.highlight(); }
|
||||||
|
this.element.addEvents(this.events);
|
||||||
|
this.fireEvent('expand', [this, e]);
|
||||||
|
},
|
||||||
|
focus: function(e) {
|
||||||
|
this.expand();
|
||||||
|
},
|
||||||
|
foundMatch: function(e) {
|
||||||
|
var typed = this.typed;
|
||||||
|
var shortlist = typed.shortlist;
|
||||||
|
var value = typed.value;
|
||||||
|
var i = 0;
|
||||||
|
var optionsLength = shortlist.length;
|
||||||
|
var excludedValues = this.options.excludedValues;
|
||||||
|
var found = false;
|
||||||
|
if (!optionsLength) { return; }
|
||||||
|
var option;
|
||||||
|
do {
|
||||||
|
option = shortlist[i];
|
||||||
|
if (option.text.toLowerCase().indexOf(value) === 0 && !excludedValues.contains(option.value)) {
|
||||||
|
found = true;
|
||||||
|
option.highlight(e);
|
||||||
|
typed.pressed = i + 1;
|
||||||
|
i = optionsLength;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
} while(i < optionsLength);
|
||||||
|
return found;
|
||||||
|
},
|
||||||
|
highlightOption: function(option) {
|
||||||
|
if (this.highlighted) { this.highlighted.removeHighlight(); }
|
||||||
|
this.highlighted = option;
|
||||||
|
},
|
||||||
|
isOpen: function() {
|
||||||
|
return this.open;
|
||||||
|
},
|
||||||
|
keydown: function(e) {
|
||||||
|
if (!this.open) { return; }
|
||||||
|
this.dropdownOptions.each(function(option) { option.disable(); });
|
||||||
|
document.addEvent('mousemove', this.bound.mousemove);
|
||||||
|
},
|
||||||
|
keypress: function(e) {
|
||||||
|
if (!this.open) { return; }
|
||||||
|
(e).stop();
|
||||||
|
|
||||||
|
var code = e.code, key = e.key;
|
||||||
|
|
||||||
|
var typed = this.typed;
|
||||||
|
var match, i, options, option, optionsLength, found, first, excludedValues, shortlist;
|
||||||
|
switch(code) {
|
||||||
|
case 38: // up
|
||||||
|
case 37: // left
|
||||||
|
if (typed.pressed > 0) { typed.pressed = typed.pressed - 1; }
|
||||||
|
if (!this.highlighted) { this.dropdownOptions.getLast().highlight(e); break; }
|
||||||
|
match = this.highlighted.element.getPrevious();
|
||||||
|
match = match ? match.retrieve('Form.SelectOption::data') : this.dropdownOptions.getLast();
|
||||||
|
match.highlight(e);
|
||||||
|
break;
|
||||||
|
case 40: // down
|
||||||
|
case 39: // right
|
||||||
|
if (typed.shortlist.length > 0) { typed.pressed = typed.pressed + 1; }
|
||||||
|
if (!this.highlighted) { this.dropdownOptions[0].highlight(e); break; }
|
||||||
|
match = this.highlighted.element.getNext();
|
||||||
|
match = match ? match.retrieve('Form.SelectOption::data') : this.dropdownOptions[0];
|
||||||
|
match.highlight(e);
|
||||||
|
break;
|
||||||
|
case 13: // enter
|
||||||
|
e.stop();
|
||||||
|
case 9: // tab - skips the stop event but selects the item
|
||||||
|
this.highlighted.select();
|
||||||
|
break;
|
||||||
|
case 27: // esc
|
||||||
|
e.stop();
|
||||||
|
this.toggle();
|
||||||
|
break;
|
||||||
|
case 32: // space
|
||||||
|
default: // anything else
|
||||||
|
if (!(code >= 48 && code <= 122 && (code <= 57 || (code >= 65 && code <= 90) || code >=97) || code === 32)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (evt.control || evt.alt || evt.meta) { return; }
|
||||||
|
// alphanumeric or space
|
||||||
|
key = code === 32 ? ' ' : key;
|
||||||
|
clearTimeout(typed.timer);
|
||||||
|
options = this.dropdownOptions;
|
||||||
|
optionsLength = options.length;
|
||||||
|
excludedValues = this.options.excludedValues;
|
||||||
|
if (typed.timer === null) { // timer is expired
|
||||||
|
typed.shortlist = [];
|
||||||
|
if (key === typed.lastKey || key === typed.startkey) { // get next
|
||||||
|
typed.pressed = typed.pressed + 1;
|
||||||
|
typed.value = key;
|
||||||
|
} else { // get first
|
||||||
|
typed = this.resetTyped();
|
||||||
|
typed.value = key;
|
||||||
|
typed.startkey = key;
|
||||||
|
typed.pressed = 1;
|
||||||
|
}
|
||||||
|
typed.timer = this.resetTyped.delay(500, this);
|
||||||
|
} else {
|
||||||
|
if (key === typed.lastKey) { // check for match, if no match get next
|
||||||
|
typed.value = typed.value + key;
|
||||||
|
if (this.foundMatch(e)) { // got a match so break
|
||||||
|
typed.timer = this.resetTyped.delay(500, this);
|
||||||
|
break;
|
||||||
|
} else { // no match fall through
|
||||||
|
typed.shortlist = [];
|
||||||
|
typed.value = key;
|
||||||
|
typed.pressed = typed.pressed + 1;
|
||||||
|
typed.timer = null;
|
||||||
|
}
|
||||||
|
} else { // reset timer, get first match, set pressed to found position
|
||||||
|
typed.timer = this.resetTyped.delay(500, this);
|
||||||
|
typed.value = typed.value + key;
|
||||||
|
typed.startkey = typed.value.substring(0, 1);
|
||||||
|
typed.lastKey = key;
|
||||||
|
this.foundMatch(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
typed.lastKey = key;
|
||||||
|
shortlist = typed.shortlist;
|
||||||
|
i = 0;
|
||||||
|
found = 0;
|
||||||
|
do {
|
||||||
|
option = options[i];
|
||||||
|
if (option.text.toLowerCase().indexOf(key) === 0 && !excludedValues.contains(option.value)) {
|
||||||
|
if (found === 0) { first = option; }
|
||||||
|
found = found + 1;
|
||||||
|
if (found === typed.pressed) { option.highlight(e); }
|
||||||
|
shortlist.push(option);
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
} while(i < optionsLength);
|
||||||
|
if (typed.pressed > found) {
|
||||||
|
first.highlight(e);
|
||||||
|
typed.pressed = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mouseenterDropdown: function() {
|
||||||
|
clearTimeout(this.collapseInterval);
|
||||||
|
},
|
||||||
|
mouseleaveDropdown: function() {
|
||||||
|
this.collapseInterval = this.options.mouseLeaveDelay ? this.collapse.delay(this.options.mouseLeaveDelay,this) : null;
|
||||||
|
},
|
||||||
|
mousemove: function() {
|
||||||
|
this.dropdownOptions.each(function(option) { option.enable(); });
|
||||||
|
document.removeEvent('mousemove', this.bound.mousemove);
|
||||||
|
},
|
||||||
|
removeHighlightOption: function(option) {
|
||||||
|
this.highlighted = null;
|
||||||
|
},
|
||||||
|
reset: function() {
|
||||||
|
if (this.options.initialValue) {
|
||||||
|
this.dropdownOptions.each(function(o) {
|
||||||
|
if (o.value === this.options.initialValue) { o.select(); }
|
||||||
|
}, this);
|
||||||
|
} else {
|
||||||
|
this.dropdownOptions[0].select();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetTyped: function() {
|
||||||
|
var typed = this.typed;
|
||||||
|
typed.value = null;
|
||||||
|
typed.timer = null;
|
||||||
|
return typed;
|
||||||
|
},
|
||||||
|
select: function(option, e) {
|
||||||
|
this.dropdownOptions.each(this.deselect);
|
||||||
|
this.selection.set('html', option.element.get('html'));
|
||||||
|
var oldValue = this.value;
|
||||||
|
this.value = option.value;
|
||||||
|
this.input.set('value', option.value);
|
||||||
|
this.selected = option;
|
||||||
|
this.fireEvent('select', [this, e]);
|
||||||
|
if (oldValue && oldValue !== this.value) { this.fireEvent('change', [this, e]); }
|
||||||
|
this.collapse(e);
|
||||||
|
},
|
||||||
|
toggle: function(e) {
|
||||||
|
if (this.open) { this.collapse(e); }
|
||||||
|
else { this.expand(e); }
|
||||||
|
},
|
||||||
|
updateOptions: function(optionElements) {
|
||||||
|
var optionsList = this.element.getElement('ul').empty(),
|
||||||
|
dropdownOptions = this.dropdownOptions.empty(),
|
||||||
|
selectOptions = this.options.selectOptions;
|
||||||
|
optionElements.each(function(opt) {
|
||||||
|
var option = new Form.SelectOption(opt, selectOptions);
|
||||||
|
option.addEvents({
|
||||||
|
'onHighlight':this.bound.highlightOption,
|
||||||
|
'onRemoveHighlight':this.bound.removeHighlightOption,
|
||||||
|
'onSelect':this.bound.select
|
||||||
|
}).owner = this;
|
||||||
|
if (option.value === this.options.initialValue || opt.get('selected')) { this.select(option); }
|
||||||
|
dropdownOptions.push(option);
|
||||||
|
optionsList.adopt(option.element);
|
||||||
|
}, this);
|
||||||
|
if (!this.selected && optionElements[0]) { optionElements[0].retrieve('Form.SelectOption::data').select(); }
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
---
|
||||||
|
name: Form.Radio
|
||||||
|
description: Class to represent a radio button
|
||||||
|
authors: Bryan J Swift (@bryanjswift)
|
||||||
|
license: MIT-style license.
|
||||||
|
requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Form-Replacement/Form.Check]
|
||||||
|
provides: Form.Radio
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
if (typeof window.Form === 'undefined') { window.Form = {}; }
|
||||||
|
|
||||||
|
Form.Radio = new Class({
|
||||||
|
Extends: Form.Check,
|
||||||
|
config: {
|
||||||
|
elementClass: 'radio',
|
||||||
|
storage: 'Form.Radio::data'
|
||||||
|
},
|
||||||
|
initialize: function(input,options) {
|
||||||
|
this.parent(input,options);
|
||||||
|
},
|
||||||
|
toggle: function(e) {
|
||||||
|
if (this.element.hasClass('checked') || this.disabled) { return; }
|
||||||
|
var evt;
|
||||||
|
if (e) { evt = new Event(e).stop(); }
|
||||||
|
if (this.checked) {
|
||||||
|
this.uncheck();
|
||||||
|
} else {
|
||||||
|
this.check();
|
||||||
|
}
|
||||||
|
this.fireEvent(this.checked ? 'onCheck' : 'onUncheck',this);
|
||||||
|
this.fireEvent('onChange',this);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
---
|
||||||
|
name: Form.SelectOption
|
||||||
|
description: Class to represent an option for Form.Dropdown
|
||||||
|
authors: Bryan J Swift (@bryanjswift)
|
||||||
|
license: MIT-style license.
|
||||||
|
requires: [Core/Class.Extras, Core/Element, Core/Element.Event]
|
||||||
|
provides: Form.SelectOption
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
if (typeof window.Form === 'undefined') { window.Form = {}; }
|
||||||
|
|
||||||
|
Form.SelectOption = new Class({
|
||||||
|
Implements: [Events, Options],
|
||||||
|
options: {
|
||||||
|
optionTag: 'li',
|
||||||
|
selected: false
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
highlightedClass: 'highlighted',
|
||||||
|
optionClass: 'option',
|
||||||
|
selectedClass: 'selected'
|
||||||
|
},
|
||||||
|
element: null,
|
||||||
|
bound: {},
|
||||||
|
option: null,
|
||||||
|
selected: false,
|
||||||
|
text: null,
|
||||||
|
value: null,
|
||||||
|
initialize: function(option, options) {
|
||||||
|
this.setOptions(options);
|
||||||
|
option = $(option);
|
||||||
|
this.option = option;
|
||||||
|
this.bound = {
|
||||||
|
deselect: this.deselect.bind(this),
|
||||||
|
highlight: this.highlight.bind(this),
|
||||||
|
removeHighlight: this.removeHighlight.bind(this),
|
||||||
|
select: this.select.bind(this)
|
||||||
|
};
|
||||||
|
this.text = option.get('text');
|
||||||
|
this.value = option.get('value');
|
||||||
|
this.element = new Element(this.options.optionTag, {
|
||||||
|
'class': (option.get('class') + ' ' + this.config.optionClass).trim(),
|
||||||
|
'html': option.get('html'),
|
||||||
|
'events': {
|
||||||
|
click: this.bound.select,
|
||||||
|
mouseenter: this.bound.highlight,
|
||||||
|
mouseleave: this.bound.removeHighlight
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.element.store('Form.SelectOption::data', this);
|
||||||
|
option.store('Form.SelectOption::data', this);
|
||||||
|
},
|
||||||
|
deselect: function(e) {
|
||||||
|
this.fireEvent('onDeselect', [this, e]);
|
||||||
|
this.element.removeClass(this.config.selectedClass).addEvent('click', this.bound.select);
|
||||||
|
this.selected = false;
|
||||||
|
},
|
||||||
|
destroy: function() {
|
||||||
|
this.element = null;
|
||||||
|
this.bound = null;
|
||||||
|
this.option = null;
|
||||||
|
},
|
||||||
|
disable: function() {
|
||||||
|
this.element.removeEvents({
|
||||||
|
mouseenter: this.bound.highlight,
|
||||||
|
mouseleave: this.bound.removeHighlight
|
||||||
|
});
|
||||||
|
this.fireEvent('onDisable', this);
|
||||||
|
},
|
||||||
|
enable: function() {
|
||||||
|
this.element.addEvents({
|
||||||
|
mouseenter: this.bound.highlight,
|
||||||
|
mouseleave: this.bound.removeHighlight
|
||||||
|
});
|
||||||
|
this.fireEvent('onEnable', this);
|
||||||
|
},
|
||||||
|
highlight: function(e) {
|
||||||
|
this.fireEvent('onHighlight', [this, e]);
|
||||||
|
this.element.addClass(this.config.highlightedClass);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
removeHighlight: function(e) {
|
||||||
|
this.fireEvent('onRemoveHighlight', [this, e]);
|
||||||
|
this.element.removeClass(this.config.highlightedClass);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
select: function(e) {
|
||||||
|
this.fireEvent('onSelect', [this, e]);
|
||||||
|
this.element.addClass(this.config.selectedClass).removeEvent('click', this.bound.select);
|
||||||
|
this.selected = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -3,6 +3,24 @@ Page.Manage = new Class({
|
|||||||
Extends: PageBase,
|
Extends: PageBase,
|
||||||
|
|
||||||
name: 'manage',
|
name: 'manage',
|
||||||
title: 'Do stuff to your existing movies!'
|
title: 'Do stuff to your existing movies!',
|
||||||
|
|
||||||
|
indexAction: function(param){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.list = new MovieList({
|
||||||
|
'status': 'done',
|
||||||
|
'actions': Manage.Action
|
||||||
|
});
|
||||||
|
$(self.list).inject(self.el);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var Manage = {
|
||||||
|
'Action': {
|
||||||
|
'IMBD': IMDBAction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
})
|
|
||||||
@@ -95,7 +95,7 @@ Page.Settings = new Class({
|
|||||||
new Element('span', {
|
new Element('span', {
|
||||||
'text': 'Show advanced settings'
|
'text': 'Show advanced settings'
|
||||||
}),
|
}),
|
||||||
self.advanced_toggle = new Element('input[type=checkbox]', {
|
self.advanced_toggle = new Element('input[type=checkbox].inlay', {
|
||||||
'events': {
|
'events': {
|
||||||
'change': self.showAdvanced.bind(self)
|
'change': self.showAdvanced.bind(self)
|
||||||
}
|
}
|
||||||
@@ -103,6 +103,10 @@ Page.Settings = new Class({
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new Form.Check(self.advanced_toggle, {
|
||||||
|
'onChange': self.showAdvanced.bind(self)
|
||||||
|
})
|
||||||
|
|
||||||
// Create tabs
|
// Create tabs
|
||||||
Object.each(self.tabs, function(tab, tab_name){
|
Object.each(self.tabs, function(tab, tab_name){
|
||||||
@@ -132,8 +136,6 @@ Page.Settings = new Class({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
self.fireEvent('create');
|
self.fireEvent('create');
|
||||||
self.openTab();
|
self.openTab();
|
||||||
|
|
||||||
@@ -150,7 +152,7 @@ Page.Settings = new Class({
|
|||||||
new Element('a', {
|
new Element('a', {
|
||||||
'href': '/'+self.name+'/'+tab_name+'/',
|
'href': '/'+self.name+'/'+tab_name+'/',
|
||||||
'text': label
|
'text': label
|
||||||
})
|
}).adopt()
|
||||||
).inject(self.tabs_container);
|
).inject(self.tabs_container);
|
||||||
|
|
||||||
if(!self.tabs[tab_name])
|
if(!self.tabs[tab_name])
|
||||||
@@ -335,7 +337,7 @@ Option.String = new Class({
|
|||||||
|
|
||||||
self.el.adopt(
|
self.el.adopt(
|
||||||
self.createLabel(),
|
self.createLabel(),
|
||||||
self.input = new Element('input', {
|
self.input = new Element('input.inlay', {
|
||||||
'type': 'text',
|
'type': 'text',
|
||||||
'name': self.postName(),
|
'name': self.postName(),
|
||||||
'value': self.getSettingValue()
|
'value': self.getSettingValue()
|
||||||
@@ -365,6 +367,11 @@ Option.Dropdown = new Class({
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.input.set('value', self.getSettingValue());
|
self.input.set('value', self.getSettingValue());
|
||||||
|
|
||||||
|
var dd = new Form.Dropdown(self.input, {
|
||||||
|
'onChange': self.changed.bind(self)
|
||||||
|
});
|
||||||
|
self.input = dd.input;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -380,13 +387,17 @@ Option.Checkbox = new Class({
|
|||||||
|
|
||||||
self.el.adopt(
|
self.el.adopt(
|
||||||
self.createLabel().set('for', randomId),
|
self.createLabel().set('for', randomId),
|
||||||
self.input = new Element('input', {
|
self.input = new Element('input.inlay', {
|
||||||
'name': self.postName(),
|
'name': self.postName(),
|
||||||
'type': 'checkbox',
|
'type': 'checkbox',
|
||||||
'checked': self.getSettingValue(),
|
'checked': self.getSettingValue(),
|
||||||
'id': randomId
|
'id': randomId
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
|
|
||||||
|
new Form.Check(self.input, {
|
||||||
|
'onChange': self.changed.bind(self)
|
||||||
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -419,7 +430,7 @@ Option.Enabler = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.el.adopt(
|
self.el.adopt(
|
||||||
self.input = new Element('input', {
|
self.input = new Element('input.inlay', {
|
||||||
'type': 'checkbox',
|
'type': 'checkbox',
|
||||||
'checked': self.getSettingValue(),
|
'checked': self.getSettingValue(),
|
||||||
'id': 'r-'+randomString(),
|
'id': 'r-'+randomString(),
|
||||||
@@ -427,7 +438,16 @@ Option.Enabler = new Class({
|
|||||||
'change': self.checkState.bind(self)
|
'change': self.checkState.bind(self)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
|
|
||||||
|
new Form.Check(self.input, {
|
||||||
|
'onChange': self.changed.bind(self)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
changed: function(){
|
||||||
|
this.parent();
|
||||||
|
this.checkState();
|
||||||
},
|
},
|
||||||
|
|
||||||
checkState: function(){
|
checkState: function(){
|
||||||
@@ -457,14 +477,13 @@ Option.Directory = new Class({
|
|||||||
type: 'span',
|
type: 'span',
|
||||||
browser: '',
|
browser: '',
|
||||||
save_on_change: false,
|
save_on_change: false,
|
||||||
show_hidden: false,
|
|
||||||
|
|
||||||
create: function(){
|
create: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.el.adopt(
|
self.el.adopt(
|
||||||
self.createLabel(),
|
self.createLabel(),
|
||||||
self.input = new Element('span', {
|
self.input = new Element('span.directory', {
|
||||||
'text': self.getSettingValue(),
|
'text': self.getSettingValue(),
|
||||||
'events': {
|
'events': {
|
||||||
'click': self.showBrowser.bind(self)
|
'click': self.showBrowser.bind(self)
|
||||||
@@ -495,12 +514,19 @@ Option.Directory = new Class({
|
|||||||
|
|
||||||
if(!self.browser)
|
if(!self.browser)
|
||||||
self.browser = new Element('div.directory_list').adopt(
|
self.browser = new Element('div.directory_list').adopt(
|
||||||
self.back_button = new Element('a.button.back', {
|
new Element('div.actions').adopt(
|
||||||
'text': '',
|
self.back_button = new Element('a.button.back', {
|
||||||
'events': {
|
'text': '',
|
||||||
'click': self.previousDirectory.bind(self)
|
'events': {
|
||||||
}
|
'click': self.previousDirectory.bind(self)
|
||||||
}),
|
}
|
||||||
|
}),
|
||||||
|
new Element('label', {
|
||||||
|
'text': 'Show hidden files'
|
||||||
|
}).adopt(
|
||||||
|
self.show_hidden = new Element('input[type=checkbox].inlay')
|
||||||
|
)
|
||||||
|
),
|
||||||
self.dir_list = new Element('ul', {
|
self.dir_list = new Element('ul', {
|
||||||
'events': {
|
'events': {
|
||||||
'click:relay(li)': self.selectDirectory.bind(self)
|
'click:relay(li)': self.selectDirectory.bind(self)
|
||||||
@@ -584,7 +610,7 @@ Option.Directory = new Class({
|
|||||||
Api.request('directory.list', {
|
Api.request('directory.list', {
|
||||||
'data': {
|
'data': {
|
||||||
'path': c,
|
'path': c,
|
||||||
'show_hidden': +self.show_hidden
|
'show_hidden': +self.show_hidden.checked
|
||||||
},
|
},
|
||||||
'onComplete': self.fillBrowser.bind(self)
|
'onComplete': self.fillBrowser.bind(self)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,208 +5,26 @@ Page.Wanted = new Class({
|
|||||||
name: 'wanted',
|
name: 'wanted',
|
||||||
title: 'Gimmy gimmy gimmy!',
|
title: 'Gimmy gimmy gimmy!',
|
||||||
|
|
||||||
movies: [],
|
|
||||||
|
|
||||||
indexAction: function(param){
|
indexAction: function(param){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.get()
|
self.list = new MovieList({
|
||||||
},
|
'status': 'active',
|
||||||
|
'actions': Wanted.Action
|
||||||
list: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if(!self.movie_container)
|
|
||||||
self.movie_container = new Element('div.movies').inject(self.el);
|
|
||||||
|
|
||||||
self.movie_container.empty();
|
|
||||||
Object.each(self.movies, function(info){
|
|
||||||
var m = new Movie(self, {}, info);
|
|
||||||
$(m).inject(self.movie_container);
|
|
||||||
m.fireEvent('injected');
|
|
||||||
});
|
});
|
||||||
|
$(self.list).inject(self.el);
|
||||||
|
|
||||||
self.movie_container.addEvents({
|
|
||||||
'mouseenter:relay(.movie)': function(e, el){
|
|
||||||
el.addClass('hover')
|
|
||||||
},
|
|
||||||
'mouseleave:relay(.movie)': function(e, el){
|
|
||||||
el.removeClass('hover')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
get: function(status, onComplete){
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
if(self.movies.length == 0)
|
|
||||||
Api.request('movie.list', {
|
|
||||||
'data': {},
|
|
||||||
'onComplete': function(json){
|
|
||||||
self.store(json.movies);
|
|
||||||
self.list();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else
|
|
||||||
self.list()
|
|
||||||
},
|
|
||||||
|
|
||||||
store: function(movies){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.movies = movies;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var Movie = new Class({
|
var Wanted = {
|
||||||
|
'Action': {
|
||||||
Extends: BlockBase,
|
'IMBD': IMDBAction
|
||||||
|
|
||||||
initialize: function(self, options, data){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.data = data;
|
|
||||||
|
|
||||||
self.profile = Quality.getProfile(data.profile_id);
|
|
||||||
self.parent(self, options);
|
|
||||||
self.addEvent('injected', self.afterInject.bind(self))
|
|
||||||
},
|
|
||||||
|
|
||||||
create: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.el = new Element('div.movie').adopt(
|
|
||||||
self.data_container = new Element('div.data', {
|
|
||||||
'tween': {
|
|
||||||
duration: 400,
|
|
||||||
transition: 'quint:in:out'
|
|
||||||
}
|
|
||||||
}).adopt(
|
|
||||||
self.thumbnail = File.Select.single('poster', self.data.library.files),
|
|
||||||
self.info_container = new Element('div.info').adopt(
|
|
||||||
self.title = new Element('div.title', {
|
|
||||||
'text': self.getTitle()
|
|
||||||
}),
|
|
||||||
self.year = new Element('div.year', {
|
|
||||||
'text': self.data.library.year || 'Unknown'
|
|
||||||
}),
|
|
||||||
self.rating = new Element('div.rating', {
|
|
||||||
'text': self.data.library.rating
|
|
||||||
}),
|
|
||||||
self.description = new Element('div.description', {
|
|
||||||
'text': self.data.library.plot
|
|
||||||
}),
|
|
||||||
self.quality = new Element('div.quality', {
|
|
||||||
'text': self.profile.get('label')
|
|
||||||
})
|
|
||||||
),
|
|
||||||
self.actions = new Element('div.actions').adopt(
|
|
||||||
self.action_imdb = new Movie.Action.IMDB(self),
|
|
||||||
self.action_edit = new Movie.Action.Edit(self),
|
|
||||||
self.action_refresh = new Movie.Action.Refresh(self),
|
|
||||||
self.action_delete = new Movie.Action.Delete(self)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if(!self.data.library.rating)
|
|
||||||
self.rating.hide();
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
afterInject: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var height = self.getHeight();
|
|
||||||
self.el.setStyle('height', height);
|
|
||||||
},
|
|
||||||
|
|
||||||
getTitle: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var titles = self.data.library.titles;
|
|
||||||
|
|
||||||
var title = titles.filter(function(title){
|
|
||||||
return title['default']
|
|
||||||
}).pop()
|
|
||||||
|
|
||||||
if(title)
|
|
||||||
return title.title
|
|
||||||
else if(titles.length > 0)
|
|
||||||
return titles[0].title
|
|
||||||
|
|
||||||
return 'Unknown movie'
|
|
||||||
},
|
|
||||||
|
|
||||||
slide: function(direction){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if(direction == 'in'){
|
|
||||||
self.el.addEvent('outerClick', self.slide.bind(self, 'out'))
|
|
||||||
self.data_container.tween('left', 0, self.getWidth());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self.el.removeEvents('outerClick')
|
|
||||||
self.data_container.tween('left', self.getWidth(), 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getHeight: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if(!self.height)
|
|
||||||
self.height = self.data_container.getCoordinates().height;
|
|
||||||
|
|
||||||
return self.height;
|
|
||||||
},
|
|
||||||
|
|
||||||
getWidth: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if(!self.width)
|
|
||||||
self.width = self.data_container.getCoordinates().width;
|
|
||||||
|
|
||||||
return self.width;
|
|
||||||
},
|
|
||||||
|
|
||||||
get: function(attr){
|
|
||||||
return this.data[attr] || this.data.library[attr]
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
Wanted.Action.Edit = new Class({
|
||||||
|
|
||||||
var MovieAction = new Class({
|
|
||||||
|
|
||||||
class_name: 'action',
|
|
||||||
|
|
||||||
initialize: function(movie){
|
|
||||||
var self = this;
|
|
||||||
self.movie = movie;
|
|
||||||
|
|
||||||
self.create();
|
|
||||||
self.el.addClass(self.class_name)
|
|
||||||
},
|
|
||||||
|
|
||||||
create: function(){},
|
|
||||||
|
|
||||||
disable: function(){
|
|
||||||
this.el.addClass('disable')
|
|
||||||
},
|
|
||||||
|
|
||||||
enable: function(){
|
|
||||||
this.el.removeClass('disable')
|
|
||||||
},
|
|
||||||
|
|
||||||
toElement: function(){
|
|
||||||
return this.el
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
Movie.Action = {}
|
|
||||||
|
|
||||||
Movie.Action.Edit = new Class({
|
|
||||||
|
|
||||||
Extends: MovieAction,
|
Extends: MovieAction,
|
||||||
|
|
||||||
@@ -229,7 +47,11 @@ Movie.Action.Edit = new Class({
|
|||||||
if(!self.options_container){
|
if(!self.options_container){
|
||||||
self.options_container = new Element('div.options').adopt(
|
self.options_container = new Element('div.options').adopt(
|
||||||
$(self.movie.thumbnail).clone(),
|
$(self.movie.thumbnail).clone(),
|
||||||
new Element('div.form').adopt(
|
new Element('div.form', {
|
||||||
|
'styles': {
|
||||||
|
'line-height': self.movie.getHeight()
|
||||||
|
}
|
||||||
|
}).adopt(
|
||||||
self.title_select = new Element('select', {
|
self.title_select = new Element('select', {
|
||||||
'name': 'title'
|
'name': 'title'
|
||||||
}),
|
}),
|
||||||
@@ -244,56 +66,49 @@ Movie.Action.Edit = new Class({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
).inject(self.movie, 'top');
|
).inject(self.movie, 'top');
|
||||||
}
|
|
||||||
|
|
||||||
|
Array.each(self.movie.data.library.titles, function(alt){
|
||||||
|
new Element('option', {
|
||||||
|
'text': alt.title
|
||||||
|
}).inject(self.title_select)
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.each(Quality.profiles, function(profile){
|
||||||
|
new Element('option', {
|
||||||
|
'value': profile.id ? profile.id : profile.data.id,
|
||||||
|
'text': profile.label ? profile.label : profile.data.label
|
||||||
|
}).inject(self.profile_select);
|
||||||
|
self.profile_select.set('value', self.movie.profile.get('id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
self.movie.slide('in');
|
self.movie.slide('in');
|
||||||
},
|
},
|
||||||
|
|
||||||
save: function(){
|
save: function(e){
|
||||||
|
(e).stop();
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
Api.request('movie.edit', {
|
Api.request('movie.edit', {
|
||||||
'data': {
|
'data': {
|
||||||
|
'id': self.movie.get('id'),
|
||||||
'default_title': self.title_select.get('value'),
|
'default_title': self.title_select.get('value'),
|
||||||
'profile_id': self.profile_select.get('value')
|
'profile_id': self.profile_select.get('value')
|
||||||
},
|
},
|
||||||
'useSpinner': true,
|
'useSpinner': true,
|
||||||
'spinnerTarget': self.movie
|
'spinnerTarget': $(self.movie),
|
||||||
})
|
'onComplete': function(){
|
||||||
}
|
self.movie.quality.set('text', self.profile_select.getSelected()[0].get('text'))
|
||||||
|
self.movie.title.set('text', self.title_select.getSelected()[0].get('text'))
|
||||||
})
|
|
||||||
|
|
||||||
Movie.Action.IMDB = new Class({
|
|
||||||
|
|
||||||
Extends: MovieAction,
|
|
||||||
id: null,
|
|
||||||
|
|
||||||
create: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.id = self.movie.get('identifier');
|
|
||||||
|
|
||||||
self.el = new Element('a.imdb', {
|
|
||||||
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
|
|
||||||
'events': {
|
|
||||||
'click': self.gotoIMDB.bind(self)
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!self.id) self.disable();
|
self.movie.slide('out');
|
||||||
},
|
|
||||||
|
|
||||||
gotoIMDB: function(e){
|
|
||||||
var self = this;
|
|
||||||
(e).stop();
|
|
||||||
|
|
||||||
window.open('http://www.imdb.com/title/'+self.id+'/');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Movie.Action.Refresh = new Class({
|
Wanted.Action.Refresh = new Class({
|
||||||
|
|
||||||
Extends: MovieAction,
|
Extends: MovieAction,
|
||||||
|
|
||||||
@@ -322,7 +137,7 @@ Movie.Action.Refresh = new Class({
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Movie.Action.Delete = new Class({
|
Wanted.Action.Delete = new Class({
|
||||||
|
|
||||||
Extends: MovieAction,
|
Extends: MovieAction,
|
||||||
|
|
||||||
@@ -360,7 +175,7 @@ Movie.Action.Delete = new Class({
|
|||||||
'text': 'or'
|
'text': 'or'
|
||||||
}),
|
}),
|
||||||
new Element('a.button.delete', {
|
new Element('a.button.delete', {
|
||||||
'text': 'Delete movie',
|
'text': 'Delete ' + self.movie.title.get('text'),
|
||||||
'events': {
|
'events': {
|
||||||
'click': self.del.bind(self)
|
'click': self.del.bind(self)
|
||||||
}
|
}
|
||||||
@@ -411,4 +226,4 @@ Movie.Action.Delete = new Class({
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
/* @override http://localhost:5000/static/style/main.css */
|
/* @override http://localhost:5000/static/style/main.css */
|
||||||
|
|
||||||
html {
|
html {
|
||||||
color: #343434;
|
color: #fff;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-family: Helvetica, Arial, Geneva, sans-serif;
|
font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
text-shadow: 0 1px 0 #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: #fff;
|
background: #4e5969;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -40,11 +41,11 @@ a img {
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration:none;
|
text-decoration:none;
|
||||||
color: #6ea1d7;
|
color: #fff;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
a:hover { color: #4d66c4; }
|
a:hover { color: #f3f3f3; }
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -99,7 +100,7 @@ form {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.spinner{
|
.spinner{
|
||||||
background: #f7f7f7 url('../images/spinner.gif') no-repeat center;
|
background: #4e5969 url('../images/spinner.gif') no-repeat center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
@@ -130,9 +131,8 @@ form {
|
|||||||
|
|
||||||
/*** Navigation ***/
|
/*** Navigation ***/
|
||||||
.header {
|
.header {
|
||||||
background: #f7f7f7;
|
background: #4e5969;
|
||||||
padding:10px;
|
padding:10px;
|
||||||
border-bottom: 1px solid #f1f1f1;
|
|
||||||
height: 60px;
|
height: 60px;
|
||||||
-moz-box-shadow: 0 0 30px rgba(0,0,0,0.1);
|
-moz-box-shadow: 0 0 30px rgba(0,0,0,0.1);
|
||||||
-webkit-box-shadow: 0 0 30px rgba(0,0,0,0.1);
|
-webkit-box-shadow: 0 0 30px rgba(0,0,0,0.1);
|
||||||
@@ -153,7 +153,7 @@ form {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.header .navigation li {
|
.header .navigation li {
|
||||||
color: #8b8b8b;
|
color: #fff;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size:20px;
|
font-size:20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -183,10 +183,151 @@ form {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header .navigation li a:link, .header .navigation li a:visited {
|
.header .navigation li a:link, .header .navigation li a:visited {
|
||||||
color: #2c2c2c;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header .navigation li a:hover, .header .navigation li a:active {
|
.header .navigation li a:hover, .header .navigation li a:active {
|
||||||
color: #8b8b8b;
|
color: #b1d8dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** Global Styles ***/
|
||||||
|
.check {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.check.highlighted { background-color: #424c59; }
|
||||||
|
.check.checked {
|
||||||
|
background-image: url('../images/checks.png');
|
||||||
|
background-position: -2px 0;
|
||||||
|
}
|
||||||
|
.check input {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select .selection {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 30px 0 20px;
|
||||||
|
|
||||||
|
border-radius:30px;
|
||||||
|
-moz-border-radius: 30px;
|
||||||
|
-webkit-border-radius: 30px;
|
||||||
|
|
||||||
|
box-shadow: 0 1px 1px rgba(0,0,0,0.35), inset 0 1px 0px rgba(255,255,255,0.20);
|
||||||
|
-moz-box-shadow: 0 1px 1px rgba(0,0,0,0.35), inset 0 1px 0px rgba(255,255,255,0.20);
|
||||||
|
-webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.35), inset 0 1px 0px rgba(255,255,255,0.20);
|
||||||
|
|
||||||
|
background: url('../images/checks.png') no-repeat 94% -53px, -webkit-gradient(
|
||||||
|
linear,
|
||||||
|
left bottom,
|
||||||
|
left top,
|
||||||
|
color-stop(0, #406db8),
|
||||||
|
color-stop(1, #5b9bd1)
|
||||||
|
);
|
||||||
|
background: url('../images/checks.png') no-repeat 94% -53px, -moz-linear-gradient(
|
||||||
|
center top,
|
||||||
|
#5b9bd1 0%,
|
||||||
|
#406db8 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select .selection .selectionDisplay {
|
||||||
|
display: inline-block;
|
||||||
|
padding-right: 15px;
|
||||||
|
border-right: 1px solid rgba(0,0,0,0.2);
|
||||||
|
|
||||||
|
box-shadow: 1px 0 0 rgba(255,255,255,0.15);
|
||||||
|
-moz-box-shadow: 1px 0 0 rgba(255,255,255,0.15);
|
||||||
|
-webkit-box-shadow: 1px 0 0 rgba(255,255,255,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select .menu {
|
||||||
|
clear: both;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.select .list {
|
||||||
|
display: none;
|
||||||
|
background: #282d34;
|
||||||
|
border: 1px solid #1f242b;
|
||||||
|
position: absolute;
|
||||||
|
margin: 28px 0 0 0;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||||
|
-moz-box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||||
|
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||||
|
border-radius:3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.select.active .list {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.select .list ul {
|
||||||
|
display: block;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.select .list li {
|
||||||
|
padding: 2px 33px 2px 20px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.select .list li.highlighted {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select input { display: none; }
|
||||||
|
|
||||||
|
.inlay {
|
||||||
|
color: #fff;
|
||||||
|
border: 0;
|
||||||
|
border-radius:3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
background: #282d34;
|
||||||
|
box-shadow: inset 0 1px 8px rgba(0,0,0,0.25), 0 1px 0px rgba(255,255,255,0.25);
|
||||||
|
-moz-box-shadow: inset 0 1px 8px rgba(0,0,0,0.25), 0 1px 0px rgba(255,255,255,0.25);
|
||||||
|
-webkit-box-shadow: inset 0 1px 8px rgba(0,0,0,0.25), 0 1px 0px rgba(255,255,255,0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inlay.light {
|
||||||
|
background: #47515f;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: inset 0 1px 8px rgba(0,0,0,0.05), 0 1px 0px rgba(255,255,255,0.15);
|
||||||
|
-moz-box-shadow: inset 0 1px 8px rgba(0,0,0,0.05), 0 1px 0px rgba(255,255,255,0.15);
|
||||||
|
-webkit-box-shadow: inset 0 1px 8px rgba(0,0,0,0.05), 0 1px 0px rgba(255,255,255,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inlay:focus {
|
||||||
|
background: #3a4350;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onlay, .inlay .selected, .inlay > li:hover {
|
||||||
|
border-radius:3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
border: 1px solid #252930;
|
||||||
|
box-shadow: inset 0 1px 0px rgba(255,255,255,0.20), 0 0 3px rgba(0,0,0, 0.2);
|
||||||
|
-moz-box-shadow: inset 0 1px 0px rgba(255,255,255,0.20), 0 0 3px rgba(0,0,0, 0.2);
|
||||||
|
-webkit-box-shadow: inset 0 1px 0px rgba(255,255,255,0.20), 0 0 3px rgba(0,0,0, 0.2);
|
||||||
|
background-image: -webkit-gradient(
|
||||||
|
linear,
|
||||||
|
left bottom,
|
||||||
|
left top,
|
||||||
|
color-stop(0, rgb(55,62,74)),
|
||||||
|
color-stop(1, rgb(73,83,98))
|
||||||
|
);
|
||||||
|
background-image: -moz-linear-gradient(
|
||||||
|
center bottom,
|
||||||
|
rgb(55,62,74) 0%,
|
||||||
|
rgb(73,83,98) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,35 +12,40 @@
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 40px 0;
|
padding: 40px 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 300px;
|
min-height: 470px;
|
||||||
|
|
||||||
|
background-image: -webkit-gradient(
|
||||||
|
linear,
|
||||||
|
right top,
|
||||||
|
40% 4%,
|
||||||
|
color-stop(0, rgba(0,0,0, 0.3)),
|
||||||
|
color-stop(0.9, rgba(0,0,0, 0))
|
||||||
|
);
|
||||||
|
background-image: -moz-linear-gradient(
|
||||||
|
30% 0% 16deg,
|
||||||
|
rgba(0,0,0,0) 0%,
|
||||||
|
rgba(0,0,0,0.3) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
.page.settings .tabs a {
|
.page.settings .tabs a {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 10px 15px;
|
padding: 11px 15px;
|
||||||
border: 1px solid transparent;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
margin-right: -1px;
|
|
||||||
}
|
}
|
||||||
.page.settings .tabs .active a {
|
.page.settings .tabs .active a {
|
||||||
color: black;
|
background: #4e5969;
|
||||||
background: #fbfbfb;
|
|
||||||
border-color: #f1f1f1;
|
|
||||||
border-right-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.page.settings .containers {
|
.page.settings .containers {
|
||||||
width: 75.8%;
|
width: 75.8%;
|
||||||
float: left;
|
float: left;
|
||||||
padding: 20px 2%;
|
padding: 20px 2%;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
background: #fbfbfb;
|
|
||||||
border-left: 1px solid #f1f1f1;s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.settings .advanced {
|
.page.settings .advanced {
|
||||||
display: none;
|
display: none;
|
||||||
color: #ce3b19;
|
color: #edc07f;
|
||||||
}
|
}
|
||||||
.page.settings.show_advanced .advanced { display: block; }
|
.page.settings.show_advanced .advanced { display: block; }
|
||||||
|
|
||||||
@@ -57,10 +62,13 @@
|
|||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
padding: 0 9px 10px 30px;
|
padding: 0 9px 10px 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-bottom: 1px solid #f3f3f3;
|
border-bottom: 1px solid #333;
|
||||||
|
|
||||||
|
box-shadow: 0 1px 0px rgba(255,255,255, 0.15);
|
||||||
|
-moz-box-shadow: 0 1px 0px rgba(255,255,255, 0.15);
|
||||||
|
-webkit-box-shadow: 0 1px 0px rgba(255,255,255, 0.15);
|
||||||
}
|
}
|
||||||
.page.settings fieldset h2 .hint {
|
.page.settings fieldset h2 .hint {
|
||||||
color: #888;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
@@ -81,26 +89,44 @@
|
|||||||
.page.settings .ctrlHolder {
|
.page.settings .ctrlHolder {
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
padding: 10px 10px 10px 30px;
|
padding: 10px 10px 10px 30px;
|
||||||
border-bottom: 1px solid #f3f3f3;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
.page.settings .ctrlHolder:last-child { border: none; }
|
.page.settings .ctrlHolder:last-child { border: none; }
|
||||||
.page.settings .ctrlHolder:hover { background: rgba(211,234,254,0.1); }
|
.page.settings .ctrlHolder:hover { background: rgba(255,255,255,0.05); }
|
||||||
.page.settings .ctrlHolder.focused:hover { background: rgba(251,246,48,0.29); }
|
.page.settings .ctrlHolder.focused { background: rgba(255,255,255,0.2); }
|
||||||
|
|
||||||
.page.settings .ctrlHolder .formHint {
|
.page.settings .ctrlHolder .formHint {
|
||||||
float: right;
|
float: right;
|
||||||
width: 47%;
|
width: 47%;
|
||||||
margin: -18px 0;
|
margin: -18px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.check {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.check.highlighted { background-color: #424c59; }
|
||||||
|
.check.checked {
|
||||||
|
background-image: url('../../images/checks.png');
|
||||||
|
background-position: -2px 0;
|
||||||
|
}
|
||||||
|
.check input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.page.settings .ctrlHolder input[type=checkbox] + .formHint {
|
.page.settings .check + .formHint {
|
||||||
float: none;
|
float: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 1% !important;
|
margin-left: 1% !important;
|
||||||
color: #222;
|
height: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.settings .ctrlHolder label {
|
.page.settings .ctrlHolder label {
|
||||||
@@ -111,8 +137,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page.settings input[type=text], .page.settings input[type=password] {
|
.page.settings input[type=text], .page.settings input[type=password] {
|
||||||
border: 1px solid #aaa;
|
padding: 5px 3px;
|
||||||
padding: 3px;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
|
|
||||||
@@ -135,5 +160,44 @@
|
|||||||
}
|
}
|
||||||
.page.settings .advanced_toggle span { padding: 0 5px; }
|
.page.settings .advanced_toggle span { padding: 0 5px; }
|
||||||
.page.settings.show_advanced .advanced_toggle {
|
.page.settings.show_advanced .advanced_toggle {
|
||||||
color: #ce3b19;
|
color: #edc07f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page.settings .directory {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
width: 29.7%;
|
||||||
|
}
|
||||||
|
.page.settings .directory_list {
|
||||||
|
position: absolute;
|
||||||
|
width: 300px;
|
||||||
|
margin: 0 0 0 16.5%;
|
||||||
|
background: #282d34;
|
||||||
|
border: 1px solid #1f242b;
|
||||||
|
position: absolute;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||||
|
-moz-box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||||
|
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
||||||
|
border-radius:3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.settings .directory_list ul {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.settings .directory_list li {
|
||||||
|
padding: 2px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.settings .directory_list .actions {
|
||||||
|
clear: both;
|
||||||
|
padding: 10px;
|
||||||
|
background: #414953;
|
||||||
|
}
|
||||||
|
.page.settings .directory_list .actions:first-child { border-bottom: 1px solid #1f242b; }
|
||||||
|
.page.settings .directory_list .actions:last-child { border-top: 1px solid #1f242b; }
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
/* @override http://localhost:5000/static/style/page/wanted.css */
|
|
||||||
|
|
||||||
.page.wanted .movies {
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page.wanted .movie {
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
background: #999;
|
|
||||||
|
|
||||||
border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
border-color: #eee #fff #fff #eee;
|
|
||||||
background: #ddd;
|
|
||||||
}
|
|
||||||
.page.wanted .movie:hover {
|
|
||||||
border-color: #ddd #fff #fff #ddd;
|
|
||||||
}
|
|
||||||
.page.wanted .movie:hover .data {
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
.page.wanted .data {
|
|
||||||
padding: 2%;
|
|
||||||
position: absolute;
|
|
||||||
width: 96.1%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
.page.wanted .data:after {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
height: 0;
|
|
||||||
clear: both;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.page.wanted .data .poster, .page.wanted .options .poster {
|
|
||||||
overflow: hidden;
|
|
||||||
float: left;
|
|
||||||
max-width: 10%;
|
|
||||||
margin: 0 2% 0 0;
|
|
||||||
border-radius:3px;
|
|
||||||
-moz-border-radius: 3px;
|
|
||||||
-webkit-border-radius: 3px;
|
|
||||||
-moz-box-shadow: 0 0 10px rgba(0,0,0,0.35);
|
|
||||||
-webkit-box-shadow: 0 0 10px rgba(0,0,0,0.35);
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page.wanted .info {
|
|
||||||
float: right;
|
|
||||||
width: 88%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page.wanted .info .title {
|
|
||||||
font-size: 30px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
float: left;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page.wanted .info .year {
|
|
||||||
font-size: 30px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
float: right;
|
|
||||||
color: #bbb;
|
|
||||||
width: 10%;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page.wanted .info .rating {
|
|
||||||
font-size: 30px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #444;
|
|
||||||
float: left;
|
|
||||||
width: 5%;
|
|
||||||
padding: 0 0 0 3%;
|
|
||||||
background: url('../../images/rating.png') no-repeat left center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page.wanted .info .description {
|
|
||||||
clear: both;
|
|
||||||
width: 95%;
|
|
||||||
}
|
|
||||||
.page.wanted .data .actions {
|
|
||||||
position: absolute;
|
|
||||||
right: 15px;
|
|
||||||
bottom: 15px;
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
.page.wanted .data:hover .action { opacity: 0.6; }
|
|
||||||
.page.wanted .data:hover .action:hover { opacity: 1; }
|
|
||||||
|
|
||||||
.page.wanted .data .action {
|
|
||||||
background: no-repeat center;
|
|
||||||
display: inline-block;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
padding: 3px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.page.wanted .data .action.refresh { background-image: url('../../images/reload.png'); }
|
|
||||||
.page.wanted .data .action.delete { background-image: url('../../images/delete.png'); }
|
|
||||||
.page.wanted .data .action.edit { background-image: url('../../images/edit.png'); }
|
|
||||||
.page.wanted .data .action.imdb { background-image: url('../../images/imdb.png'); }
|
|
||||||
|
|
||||||
.page.wanted .delete_container {
|
|
||||||
clear: both;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 20px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.page.wanted .delete_container .cancel {
|
|
||||||
}
|
|
||||||
.page.wanted .delete_container .or {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.page.wanted .delete_container .delete {
|
|
||||||
background-color: #ff321c;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
.page.wanted .delete_container .delete:hover {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #d32917;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page.wanted .options {
|
|
||||||
padding: 2%;
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,11 @@
|
|||||||
<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/library/mootools_more.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/library/uniform.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/form_replacement/form_check.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/form_replacement/form_radio.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/form_replacement/form_dropdown.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/form_replacement/form_selectoption.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/library/history.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/library/history.js') }}"></script>
|
||||||
|
|
||||||
@@ -19,6 +24,13 @@
|
|||||||
<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>
|
||||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/block/footer.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/block/footer.js') }}"></script>
|
||||||
|
|
||||||
|
{% for url in fireEvent('clientscript.get_scripts', as_html = True, single = True) %}
|
||||||
|
<script type="text/javascript" src="{{ url_for('web.index') }}{{ url }}"></script>
|
||||||
|
{% endfor %}
|
||||||
|
{% for url in fireEvent('clientscript.get_styles', as_html = True, single = True) %}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('web.index') }}{{ url }}" type="text/css">
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/wanted.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/wanted.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/settings.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('.static', filename='scripts/page/settings.js') }}"></script>
|
||||||
@@ -26,11 +38,6 @@
|
|||||||
<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>
|
||||||
|
|
||||||
{% for url in fireEvent('clientscript.get_scripts', as_html = True, single = True) %}
|
|
||||||
<script type="text/javascript" src="{{ url_for('web.index') }}{{ url }}"></script>{% endfor %}
|
|
||||||
{% for url in fireEvent('clientscript.get_styles', as_html = True, single = True) %}
|
|
||||||
<link rel="stylesheet" href="{{ url_for('web.index') }}{{ url }}" type="text/css">{% endfor %}
|
|
||||||
|
|
||||||
<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" />
|
||||||
<link rel="apple-touch-icon" href="{{ url_for('.static', filename='images/homescreen.png') }}" />
|
<link rel="apple-touch-icon" href="{{ url_for('.static', filename='images/homescreen.png') }}" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user