Renamer, movie searcher, Library scanning
New theme for UI and more..
This commit is contained in:
@@ -4,6 +4,8 @@ from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Quality, Profile, ProfileType
|
||||
import os.path
|
||||
import re
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -11,10 +13,10 @@ log = CPLog(__name__)
|
||||
class QualityPlugin(Plugin):
|
||||
|
||||
qualities = [
|
||||
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', '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': '720p', 'size': (3500, 10000), 'label': '720P', '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': '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', 'width': 1920, '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':['avi']},
|
||||
{'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': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['dvdscr'], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||
@@ -28,6 +30,7 @@ class QualityPlugin(Plugin):
|
||||
def __init__(self):
|
||||
addEvent('quality.all', self.all)
|
||||
addEvent('quality.single', self.single)
|
||||
addEvent('quality.guess', self.guess)
|
||||
addEvent('app.load', self.fill)
|
||||
|
||||
path = self.registerStatic(__file__)
|
||||
@@ -50,9 +53,11 @@ class QualityPlugin(Plugin):
|
||||
def single(self, identifier = ''):
|
||||
|
||||
db = get_session()
|
||||
quality_dict = {}
|
||||
|
||||
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
|
||||
|
||||
@@ -110,3 +115,41 @@ class QualityPlugin(Plugin):
|
||||
db.commit()
|
||||
|
||||
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.createProfiles();
|
||||
self.createOrdering();
|
||||
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
|
||||
*/
|
||||
createSizes: function(){
|
||||
var self = this;
|
||||
|
||||
self.settings.createGroup({
|
||||
var group = self.settings.createGroup({
|
||||
'label': 'Sizes',
|
||||
'description': 'Discriptions',
|
||||
'advanced': true
|
||||
}).inject(self.content)
|
||||
}
|
||||
|
||||
});
|
||||
window.Quality = new QualityBase();
|
||||
|
||||
var Profile = new Class({
|
||||
|
||||
data: {},
|
||||
types: [],
|
||||
|
||||
initialize: function(data){
|
||||
var self = this;
|
||||
|
||||
self.data = data;
|
||||
self.types = [];
|
||||
|
||||
self.create();
|
||||
|
||||
self.el.addEvents({
|
||||
'change:relay(select, input[type=checkbox])': self.save.bind(self, 0),
|
||||
'keyup:relay(input[type=text])': self.save.bind(self, [300])
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
var data = self.data;
|
||||
|
||||
self.el = new Element('div.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
|
||||
});
|
||||
|
||||
new Element('div.item.header').adopt(
|
||||
new Element('span.label', {'text': 'Quality'}),
|
||||
new Element('span.min', {'text': 'Min'}),
|
||||
new Element('span.max', {'text': 'Max'})
|
||||
).inject(group)
|
||||
|
||||
Object.each(self.qualities, function(quality){
|
||||
new Element('div.item').adopt(
|
||||
new Element('span.label', {'text': quality.label}),
|
||||
new Element('input.min', {'value': quality.size_min}),
|
||||
new Element('input.max', {'value': quality.size_max})
|
||||
).inject(group)
|
||||
})
|
||||
|
||||
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
|
||||
|
||||
p(group)
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
})
|
||||
window.Quality = new QualityBase();
|
||||
|
||||
Reference in New Issue
Block a user