Merge branch 'refs/heads/develop' into redesign
Conflicts: couchpotato/static/scripts/couchpotato.js
This commit is contained in:
commit
58acd53a9a
65 files changed
+1204
-388
No files matched your search
@@ -113,7 +113,7 @@ class Plugin(object):
|
||||
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f)
|
||||
|
||||
def createFile(self, path, content, binary = False):
|
||||
path = ss(path)
|
||||
path = sp(path)
|
||||
|
||||
self.makeDir(os.path.dirname(path))
|
||||
|
||||
@@ -131,7 +131,7 @@ class Plugin(object):
|
||||
os.remove(path)
|
||||
|
||||
def makeDir(self, path):
|
||||
path = ss(path)
|
||||
path = sp(path)
|
||||
try:
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path, Env.getPermission('folder'))
|
||||
|
||||
@@ -79,7 +79,20 @@ class Dashboard(Plugin):
|
||||
# Don't list older movies
|
||||
if ((not late and (media['info']['year'] >= now_year - 1) and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or
|
||||
(late and (media['info']['year'] < now_year - 1 or (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200)))):
|
||||
medias.append(media)
|
||||
|
||||
add = True
|
||||
|
||||
# Check if it doesn't have any releases
|
||||
if late:
|
||||
media['releases'] = fireEvent('release.for_media', media['_id'], single = True)
|
||||
|
||||
for release in media.get('releases'):
|
||||
if release.get('status') in ['snatched', 'available', 'seeding', 'downloaded']:
|
||||
add = False
|
||||
break
|
||||
|
||||
if add:
|
||||
medias.append(media)
|
||||
|
||||
if len(medias) >= limit:
|
||||
break
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
@@ -22,7 +23,11 @@ class Logging(Plugin):
|
||||
},
|
||||
'return': {'type': 'object', 'example': """{
|
||||
'success': True,
|
||||
'log': string, //Log file
|
||||
'log': [{
|
||||
'time': '03-12 09:12:59',
|
||||
'type': 'INFO',
|
||||
'message': 'Log message'
|
||||
}, ..], //Log file
|
||||
'total': int, //Total log files available
|
||||
}"""}
|
||||
})
|
||||
@@ -34,7 +39,11 @@ class Logging(Plugin):
|
||||
},
|
||||
'return': {'type': 'object', 'example': """{
|
||||
'success': True,
|
||||
'log': string, //Log file
|
||||
'log': [{
|
||||
'time': '03-12 09:12:59',
|
||||
'type': 'INFO',
|
||||
'message': 'Log message'
|
||||
}, ..]
|
||||
}"""}
|
||||
})
|
||||
addApiView('logging.clear', self.clear, docs = {
|
||||
@@ -71,16 +80,18 @@ class Logging(Plugin):
|
||||
if current_path:
|
||||
f = open(current_path, 'r')
|
||||
log_content = f.read()
|
||||
logs = self.toList(log_content)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'log': toUnicode(log_content),
|
||||
'log': logs,
|
||||
'total': total,
|
||||
}
|
||||
|
||||
def partial(self, type = 'all', lines = 30, **kwargs):
|
||||
def partial(self, type = 'all', lines = 30, offset = 0, **kwargs):
|
||||
|
||||
total_lines = tryInt(lines)
|
||||
offset = tryInt(offset)
|
||||
|
||||
log_lines = []
|
||||
|
||||
@@ -93,28 +104,57 @@ class Logging(Plugin):
|
||||
break
|
||||
|
||||
f = open(path, 'r')
|
||||
reversed_lines = toUnicode(f.read()).split('[0m\n')
|
||||
reversed_lines.reverse()
|
||||
log_content = toUnicode(f.read())
|
||||
raw_lines = self.toList(log_content)
|
||||
raw_lines.reverse()
|
||||
|
||||
brk = False
|
||||
for line in reversed_lines:
|
||||
for line in raw_lines:
|
||||
|
||||
if type == 'all' or '%s ' % type.upper() in line:
|
||||
if type == 'all' or line.get('type') == type.upper():
|
||||
log_lines.append(line)
|
||||
|
||||
if len(log_lines) >= total_lines:
|
||||
if len(log_lines) >= (total_lines + offset):
|
||||
brk = True
|
||||
break
|
||||
|
||||
if brk:
|
||||
break
|
||||
|
||||
log_lines = log_lines[offset:]
|
||||
log_lines.reverse()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'log': '[0m\n'.join(log_lines),
|
||||
'log': log_lines,
|
||||
}
|
||||
|
||||
def toList(self, log_content = ''):
|
||||
|
||||
logs_raw = toUnicode(log_content).split('[0m\n')
|
||||
|
||||
logs = []
|
||||
for log in logs_raw:
|
||||
split = splitString(log, '\x1b')
|
||||
if split:
|
||||
try:
|
||||
date, time, log_type = splitString(split[0], ' ')
|
||||
timestamp = '%s %s' % (date, time)
|
||||
except:
|
||||
timestamp = 'UNKNOWN'
|
||||
log_type = 'UNKNOWN'
|
||||
|
||||
message = ''.join(split[1]) if len(split) > 1 else split[0]
|
||||
message = re.sub('\[\d+m\[', '[', message)
|
||||
|
||||
logs.append({
|
||||
'time': timestamp,
|
||||
'type': log_type,
|
||||
'message': message
|
||||
})
|
||||
|
||||
return logs
|
||||
|
||||
def clear(self, **kwargs):
|
||||
|
||||
for x in range(0, 50):
|
||||
|
||||
@@ -16,10 +16,14 @@
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page.log .nav li.select,
|
||||
.page.log .nav li.clear {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page.log .nav li:hover:not(.active) {
|
||||
.page.log .nav li:hover:not(.active, .filter) {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
@@ -39,6 +43,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.page.log .nav li.hint {
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
left: 50%;
|
||||
margin-left: -200px;
|
||||
font-style: italic;
|
||||
font-size: 11px;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
opacity: .5;
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
.page.log .loading {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
@@ -50,28 +67,133 @@
|
||||
overflow: hidden;
|
||||
line-height: 150%;
|
||||
font-size: 11px;
|
||||
font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.page.log .container .error {
|
||||
color: #FFA4A4;
|
||||
white-space: pre-wrap;
|
||||
.page.log .container select {
|
||||
vertical-align: top;
|
||||
}
|
||||
.page.log .container .debug { color: lightgrey; }
|
||||
|
||||
.page.log .container .time {
|
||||
clear: both;
|
||||
color: lightgrey;
|
||||
padding: 3px 0;
|
||||
font-size: 10px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 0 3px;
|
||||
font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif;
|
||||
}
|
||||
|
||||
.page.log .container .time:last-child { display: none; }
|
||||
|
||||
.page.log .container .time.highlight {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.page.log .container .time span {
|
||||
float: right;
|
||||
width: 86%;
|
||||
padding: 5px 0 3px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.page.log[data-filter=INFO] .error,
|
||||
.page.log[data-filter=INFO] .debug,
|
||||
.page.log[data-filter=ERROR] .debug,
|
||||
.page.log[data-filter=ERROR] .info,
|
||||
.page.log[data-filter=DEBUG] .info,
|
||||
.page.log[data-filter=DEBUG] .error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page.log .container .type {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.page.log .container .message {
|
||||
float: right;
|
||||
width: 86%;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.page.log .container .error { color: #FFA4A4; }
|
||||
.page.log .container .debug span { opacity: .6; }
|
||||
|
||||
.do_report {
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.page.log .report {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,.7);
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page.log .report .button {
|
||||
display: inline-block;
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.page.log .report .bug {
|
||||
width: 800px;
|
||||
height: 80%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin: 0 0 0 -400px;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
|
||||
.page.log .report .bug textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background: #FFF;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
color: #666;
|
||||
height: 70%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page.log .container .time ::-webkit-selection {
|
||||
background-color: #000;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.page.log .container .time ::-moz-selection {
|
||||
background-color: #000;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.page.log .container .time ::-ms-selection {
|
||||
background-color: #000;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.page.log .container .time.highlight ::selection {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.page.log .container .time.highlight ::-webkit-selection {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.page.log .container .time.highlight ::-moz-selection {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.page.log .container .time.highlight ::-ms-selection {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.page.log .container .time.highlight ::selection {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
@@ -2,80 +2,281 @@ Page.Log = new Class({
|
||||
|
||||
Extends: PageBase,
|
||||
|
||||
order: 60,
|
||||
name: 'log',
|
||||
title: 'Show recent logs.',
|
||||
has_tab: false,
|
||||
|
||||
indexAction: function(){
|
||||
log_items: [],
|
||||
report_text: '\
|
||||
### Steps to reproduce:\n\
|
||||
1. ..\n\
|
||||
2. ..\n\
|
||||
\n\
|
||||
### Information:\n\
|
||||
Movie(s) I have this with: ...\n\
|
||||
Quality of the movie being searched: ...\n\
|
||||
Providers I use: ...\n\
|
||||
Version of CouchPotato: ...\n\
|
||||
Running on: ...\n\
|
||||
\n\
|
||||
### Logs:\n\
|
||||
```\n{issue}```',
|
||||
|
||||
indexAction: function () {
|
||||
var self = this;
|
||||
|
||||
self.getLogs(0);
|
||||
|
||||
},
|
||||
|
||||
getLogs: function(nr){
|
||||
getLogs: function (nr) {
|
||||
var self = this;
|
||||
|
||||
if(self.log) self.log.destroy();
|
||||
if (self.log) self.log.destroy();
|
||||
self.log = new Element('div.container.loading', {
|
||||
'text': 'loading...'
|
||||
'text': 'loading...',
|
||||
'events': {
|
||||
'mouseup:relay(.time)': function(e){
|
||||
self.showSelectionButton.delay(100, self, e);
|
||||
}
|
||||
}
|
||||
}).inject(self.el);
|
||||
|
||||
Api.request('logging.get', {
|
||||
'data': {
|
||||
'nr': nr
|
||||
},
|
||||
'onComplete': function(json){
|
||||
self.log.set('html', self.addColors(json.log));
|
||||
'onComplete': function (json) {
|
||||
self.log.set('text', '');
|
||||
self.log_items = self.createLogElements(json.log);
|
||||
self.log.adopt(self.log_items);
|
||||
self.log.removeClass('loading');
|
||||
|
||||
new Fx.Scroll(window, {'duration': 0}).toBottom();
|
||||
var nav = new Element('ul.nav', {
|
||||
'events': {
|
||||
'click:relay(li.select)': function (e, el) {
|
||||
self.getLogs(parseInt(el.get('text')) - 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var nav = new Element('ul.nav').inject(self.log, 'top');
|
||||
for (var i = 0; i <= json.total; i++) {
|
||||
new Element('li', {
|
||||
'text': i+1,
|
||||
'class': nr == i ? 'active': '',
|
||||
// Type selection
|
||||
new Element('li.filter').grab(
|
||||
new Element('select', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
self.getLogs(e.target.get('text')-1);
|
||||
'change': function () {
|
||||
var type_filter = this.getSelected()[0].get('value');
|
||||
self.el.set('data-filter', type_filter);
|
||||
self.scrollToBottom();
|
||||
}
|
||||
}
|
||||
}).adopt(
|
||||
new Element('option', {'value': 'ALL', 'text': 'Show all logs'}),
|
||||
new Element('option', {'value': 'INFO', 'text': 'Show only INFO'}),
|
||||
new Element('option', {'value': 'DEBUG', 'text': 'Show only DEBUG'}),
|
||||
new Element('option', {'value': 'ERROR', 'text': 'Show only ERROR'})
|
||||
)
|
||||
).inject(nav);
|
||||
|
||||
// Selections
|
||||
for (var i = 0; i <= json.total; i++) {
|
||||
new Element('li', {
|
||||
'text': i + 1,
|
||||
'class': 'select ' + (nr == i ? 'active' : '')
|
||||
}).inject(nav);
|
||||
}
|
||||
|
||||
new Element('li', {
|
||||
// Clear button
|
||||
new Element('li.clear', {
|
||||
'text': 'clear',
|
||||
'events': {
|
||||
'click': function(){
|
||||
'click': function () {
|
||||
Api.request('logging.clear', {
|
||||
'onComplete': function(){
|
||||
'onComplete': function () {
|
||||
self.getLogs(0);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}).inject(nav)
|
||||
}).inject(nav);
|
||||
|
||||
// Hint
|
||||
new Element('li.hint', {
|
||||
'text': 'Select multiple lines & report an issue'
|
||||
}).inject(nav);
|
||||
|
||||
// Add to page
|
||||
nav.inject(self.log, 'top');
|
||||
|
||||
self.scrollToBottom();
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
addColors: function(text){
|
||||
createLogElements: function (logs) {
|
||||
|
||||
text = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/\u001b\[31m/gi, '</span><span class="error">')
|
||||
.replace(/\u001b\[36m/gi, '</span><span class="debug">')
|
||||
.replace(/\u001b\[33m/gi, '</span><span class="debug">')
|
||||
.replace(/\u001b\[0m\n/gi, '</div><div class="time">')
|
||||
.replace(/\u001b\[0m/gi, '</span><span>');
|
||||
var elements = [];
|
||||
|
||||
return '<div class="time">' + text + '</div>';
|
||||
logs.each(function (log) {
|
||||
elements.include(new Element('div', {
|
||||
'class': 'time ' + log.type.toLowerCase()
|
||||
}).adopt(
|
||||
new Element('span', {
|
||||
'text': log.time
|
||||
}),
|
||||
new Element('span.type', {
|
||||
'text': log.type
|
||||
}),
|
||||
new Element('span.message', {
|
||||
'text': log.message
|
||||
})
|
||||
))
|
||||
});
|
||||
|
||||
return elements;
|
||||
},
|
||||
|
||||
scrollToBottom: function () {
|
||||
new Fx.Scroll(window, {'duration': 0}).toBottom();
|
||||
},
|
||||
|
||||
showSelectionButton: function(e){
|
||||
var self = this,
|
||||
selection = self.getSelected(),
|
||||
start_node = selection.anchorNode,
|
||||
parent_start = start_node.parentNode.getParent('.time'),
|
||||
end_node = selection.focusNode.parentNode.getParent('.time'),
|
||||
text = '';
|
||||
|
||||
var remove_button = function(){
|
||||
self.log.getElements('.highlight').removeClass('highlight');
|
||||
if(self.do_report)
|
||||
self.do_report.destroy();
|
||||
document.body.removeEvent('click', remove_button);
|
||||
};
|
||||
remove_button();
|
||||
|
||||
if(parent_start)
|
||||
start_node = parent_start;
|
||||
|
||||
var index = {
|
||||
'start': self.log_items.indexOf(start_node),
|
||||
'end': self.log_items.indexOf(end_node)
|
||||
};
|
||||
|
||||
if(index.start > index.end){
|
||||
index = {
|
||||
'start': index.end,
|
||||
'end': index.start
|
||||
};
|
||||
}
|
||||
|
||||
var nodes = self.log_items.slice(index.start, index.end + 1);
|
||||
|
||||
nodes.each(function(node, nr){
|
||||
node.addClass('highlight');
|
||||
node.getElements('span').each(function(span){
|
||||
text += self.spaceFill(span.get('text') + ' ', 6);
|
||||
});
|
||||
text += '\n';
|
||||
});
|
||||
|
||||
self.do_report = new Element('a.do_report.button', {
|
||||
'text': 'Report issue',
|
||||
'styles': {
|
||||
'top': e.page.y,
|
||||
'left': e.page.x
|
||||
},
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stop();
|
||||
|
||||
self.showReport(text);
|
||||
}
|
||||
}
|
||||
}).inject(document.body);
|
||||
|
||||
setTimeout(function(){
|
||||
document.body.addEvent('click', remove_button);
|
||||
}, 0);
|
||||
|
||||
},
|
||||
|
||||
showReport: function(text){
|
||||
var self = this,
|
||||
body = self.report_text.replace('{issue}', text);
|
||||
|
||||
var overlay = new Element('div.report', {
|
||||
'method': 'post',
|
||||
'events': {
|
||||
'click': function(e){
|
||||
overlay.destroy();
|
||||
}
|
||||
}
|
||||
}).grab(
|
||||
new Element('div.bug', {
|
||||
'events': {
|
||||
'click': function(e){
|
||||
(e).stopPropagation();
|
||||
}
|
||||
}
|
||||
}).adopt(
|
||||
new Element('h1', {
|
||||
'text': 'Report a bug'
|
||||
}),
|
||||
new Element('span').adopt(
|
||||
new Element('span', {
|
||||
'text': 'Read '
|
||||
}),
|
||||
new Element('a.button', {
|
||||
'target': '_blank',
|
||||
'text': 'the contributing guide',
|
||||
'href': 'https://github.com/RuudBurger/CouchPotatoServer/blob/develop/contributing.md'
|
||||
}),
|
||||
new Element('span', {
|
||||
'text': ' before posting, then copy the text below'
|
||||
})
|
||||
),
|
||||
new Element('textarea', {
|
||||
'text': body,
|
||||
'events': {
|
||||
'click': function(){
|
||||
this.select();
|
||||
}
|
||||
}
|
||||
}),
|
||||
new Element('a.button', {
|
||||
'target': '_blank',
|
||||
'text': 'Create a new issue on GitHub with the text above',
|
||||
'href': 'https://github.com/RuudBurger/CouchPotatoServer/issues/new?body=' + (body.length < 2000 ? encodeURIComponent(body) : 'Paste the text here')
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
overlay.inject(self.log);
|
||||
},
|
||||
|
||||
getSelected: function(){
|
||||
if (window.getSelection)
|
||||
return window.getSelection();
|
||||
else if (document.getSelection)
|
||||
return document.getSelection();
|
||||
else {
|
||||
var selection = document.selection && document.selection.createRange();
|
||||
if (selection.text)
|
||||
return selection.text;
|
||||
}
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
spaceFill: function( number, width ){
|
||||
if ( number.toString().length >= width )
|
||||
return number;
|
||||
return ( new Array( width ).join( ' ' ) + number.toString() ).substr( -width );
|
||||
}
|
||||
|
||||
});
|
||||
@@ -120,7 +120,7 @@ class Manage(Plugin):
|
||||
if self.conf('cleanup') and full and not self.shuttingDown():
|
||||
|
||||
# Get movies with done status
|
||||
total_movies, done_movies = fireEvent('media.list', types = 'movie', status = 'done', single = True)
|
||||
total_movies, done_movies = fireEvent('media.list', types = 'movie', status = 'done', release_status = 'done', status_or = True, single = True)
|
||||
|
||||
for done_movie in done_movies:
|
||||
if getIdentifier(done_movie) not in added_identifiers:
|
||||
@@ -131,12 +131,16 @@ class Manage(Plugin):
|
||||
|
||||
for release in releases:
|
||||
if release.get('files'):
|
||||
brk = False
|
||||
for file_type in release.get('files', {}):
|
||||
for release_file in release['files'][file_type]:
|
||||
# Remove release not available anymore
|
||||
if not os.path.isfile(sp(release_file)):
|
||||
fireEvent('release.clean', release['_id'])
|
||||
brk = True
|
||||
break
|
||||
if brk:
|
||||
break
|
||||
|
||||
# Check if there are duplicate releases (different quality) use the last one, delete the rest
|
||||
if len(releases) > 1:
|
||||
@@ -147,10 +151,12 @@ class Manage(Plugin):
|
||||
already_used = used_files.get(release_file)
|
||||
|
||||
if already_used:
|
||||
# delete current one
|
||||
if already_used.get('last_edit', 0) < release.get('last_edit', 0):
|
||||
fireEvent('release.delete', release['_id'], single = True) # delete current one
|
||||
fireEvent('release.delete', release['_id'], single = True)
|
||||
# delete previous one
|
||||
else:
|
||||
fireEvent('release.delete', already_used['_id'], single = True) # delete previous one
|
||||
fireEvent('release.delete', already_used['_id'], single = True)
|
||||
break
|
||||
else:
|
||||
used_files[release_file] = release
|
||||
|
||||
@@ -34,7 +34,7 @@ class ProfilePlugin(Plugin):
|
||||
})
|
||||
|
||||
addEvent('app.initialize', self.fill, priority = 90)
|
||||
addEvent('app.load', self.forceDefaults)
|
||||
addEvent('app.load', self.forceDefaults, priority = 110)
|
||||
|
||||
def forceDefaults(self):
|
||||
|
||||
@@ -87,7 +87,7 @@ class ProfilePlugin(Plugin):
|
||||
order = 0
|
||||
for type in kwargs.get('types', []):
|
||||
profile['qualities'].append(type.get('quality'))
|
||||
profile['wait_for'].append(tryInt(type.get('wait_for')))
|
||||
profile['wait_for'].append(tryInt(kwargs.get('wait_for', 0)))
|
||||
profile['finish'].append((tryInt(type.get('finish')) == 1) if order > 0 else True)
|
||||
profile['3d'].append(tryInt(type.get('3d')))
|
||||
order += 1
|
||||
|
||||
@@ -41,7 +41,7 @@ var Profile = new Class({
|
||||
new Element('span', {'text':'Wait'}),
|
||||
new Element('input.inlay.xsmall', {
|
||||
'type':'text',
|
||||
'value': data.types && data.types.length > 0 ? data.types[0].wait_for : 0
|
||||
'value': data.wait_for && data.wait_for.length > 0 ? data.wait_for[0] : 0
|
||||
}),
|
||||
new Element('span', {'text':'day(s) for a better quality.'})
|
||||
),
|
||||
@@ -63,8 +63,7 @@ var Profile = new Class({
|
||||
data.types.include({
|
||||
'quality': quality,
|
||||
'finish': data.finish[nr] || false,
|
||||
'3d': data['3d'] ? data['3d'][nr] || false : false,
|
||||
'wait_for': data.wait_for[nr] || 0
|
||||
'3d': data['3d'] ? data['3d'][nr] || false : false
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -126,8 +125,7 @@ var Profile = new Class({
|
||||
data.types.include({
|
||||
'quality': type.getElement('select').get('value'),
|
||||
'finish': +type.getElement('input.finish[type=checkbox]').checked,
|
||||
'3d': +type.getElement('input.3d[type=checkbox]').checked,
|
||||
'wait_for': 0
|
||||
'3d': +type.getElement('input.3d[type=checkbox]').checked
|
||||
});
|
||||
});
|
||||
|
||||
@@ -340,8 +338,7 @@ Profile.Type = new Class({
|
||||
return {
|
||||
'quality': self.qualities.get('value'),
|
||||
'finish': +self.finish.checked,
|
||||
'3d': +self['3d'].checked,
|
||||
'wait_for': 0
|
||||
'3d': +self['3d'].checked
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ class QualityPlugin(Plugin):
|
||||
}
|
||||
|
||||
qualities = [
|
||||
{'identifier': 'bd50', 'hd': True, 'allow_3d': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
|
||||
{'identifier': 'bd50', 'hd': True, 'allow_3d': True, 'size': (20000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':['iso', 'img'], 'tags': ['bdmv', 'certificate', ('complete', 'bluray'), 'avc', 'mvc']},
|
||||
{'identifier': '1080p', 'hd': True, 'allow_3d': True, 'size': (4000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts', 'x264', 'h264']},
|
||||
{'identifier': '720p', 'hd': True, 'allow_3d': True, 'size': (3000, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts'], 'tags': ['x264', 'h264']},
|
||||
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':[], 'tags': ['hdtv', 'hdrip', 'webdl', ('web', 'dl')]},
|
||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': ['br2dvd'], 'allow': [], 'ext':['iso', 'img', 'vob'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts', ('dvd', 'r')]},
|
||||
{'identifier': 'brrip', 'hd': True, 'allow_3d': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':[], 'tags': ['hdtv', 'hdrip', 'webdl', ('web', 'dl')]},
|
||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': ['br2dvd'], 'allow': [], 'ext':['iso', 'img', 'vob'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts', ('dvd', 'r'), 'dvd9']},
|
||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': [], 'allow': [], 'ext':[], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
|
||||
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip', 'dvdscreener', 'hdscr'], 'allow': ['dvdr', 'dvdrip', '720p', '1080p'], 'ext':[], 'tags': ['webrip', ('web', 'rip')]},
|
||||
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': ['r6'], 'allow': ['dvdr'], 'ext':[]},
|
||||
@@ -35,9 +35,9 @@ class QualityPlugin(Plugin):
|
||||
]
|
||||
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
||||
threed_tags = {
|
||||
'hsbs': [('half', 'sbs')],
|
||||
'fsbs': [('full', 'sbs')],
|
||||
'3d': [],
|
||||
'sbs': [('half', 'sbs'), 'hsbs', ('full', 'sbs'), 'fsbs'],
|
||||
'ou': [('half', 'ou'), 'hou', ('full', 'ou'), 'fou'],
|
||||
'3d': ['2d3d', '3d2d'],
|
||||
}
|
||||
|
||||
cached_qualities = None
|
||||
@@ -49,6 +49,8 @@ class QualityPlugin(Plugin):
|
||||
addEvent('quality.guess', self.guess)
|
||||
addEvent('quality.pre_releases', self.preReleases)
|
||||
addEvent('quality.order', self.getOrder)
|
||||
addEvent('quality.ishigher', self.isHigher)
|
||||
addEvent('quality.isfinish', self.isFinish)
|
||||
|
||||
addApiView('quality.size.save', self.saveSize)
|
||||
addApiView('quality.list', self.allView, docs = {
|
||||
@@ -177,7 +179,7 @@ class QualityPlugin(Plugin):
|
||||
|
||||
return False
|
||||
|
||||
def guess(self, files, extra = None):
|
||||
def guess(self, files, extra = None, size = None):
|
||||
if not extra: extra = {}
|
||||
|
||||
# Create hash for cache
|
||||
@@ -205,15 +207,20 @@ class QualityPlugin(Plugin):
|
||||
|
||||
self.calcScore(score, quality, contains_score, threedscore)
|
||||
|
||||
# Evaluate score based on size
|
||||
for quality in qualities:
|
||||
size_score = self.guessSizeScore(quality, size = size)
|
||||
self.calcScore(score, quality, size_score, penalty = False)
|
||||
|
||||
# Try again with loose testing
|
||||
for quality in qualities:
|
||||
loose_score = self.guessLooseScore(quality, extra = extra)
|
||||
self.calcScore(score, quality, loose_score)
|
||||
self.calcScore(score, quality, loose_score, penalty = False)
|
||||
|
||||
# Return nothing if all scores are 0
|
||||
# Return nothing if all scores are <= 0
|
||||
has_non_zero = 0
|
||||
for s in score:
|
||||
if score[s] > 0:
|
||||
if score[s]['score'] > 0:
|
||||
has_non_zero += 1
|
||||
|
||||
if not has_non_zero:
|
||||
@@ -281,7 +288,7 @@ class QualityPlugin(Plugin):
|
||||
return 1, key
|
||||
|
||||
if list(set([key]) & set(words)):
|
||||
log.debug('Found %s in %s', (tag, cur_file))
|
||||
log.debug('Found %s in %s', (key, cur_file))
|
||||
return 1, key
|
||||
|
||||
return 0, None
|
||||
@@ -308,7 +315,20 @@ class QualityPlugin(Plugin):
|
||||
|
||||
return score
|
||||
|
||||
def calcScore(self, score, quality, add_score, threedscore = (0, None)):
|
||||
|
||||
def guessSizeScore(self, quality, size = None):
|
||||
|
||||
score = 0
|
||||
|
||||
if size:
|
||||
|
||||
if tryInt(quality['size_min']) <= tryInt(size) <= tryInt(quality['size_max']):
|
||||
log.debug('Found %s via release size: %s MB < %s MB < %s MB', (quality['identifier'], quality['size_min'], size, quality['size_max']))
|
||||
score += 5
|
||||
|
||||
return score
|
||||
|
||||
def calcScore(self, score, quality, add_score, threedscore = (0, None), penalty = True):
|
||||
|
||||
score[quality['identifier']]['score'] += add_score
|
||||
|
||||
@@ -325,30 +345,79 @@ class QualityPlugin(Plugin):
|
||||
for q in self.qualities:
|
||||
self.cached_order[q.get('identifier')] = self.qualities.index(q)
|
||||
|
||||
if add_score != 0:
|
||||
if penalty and add_score != 0:
|
||||
for allow in quality.get('allow', []):
|
||||
score[allow]['score'] -= 40 if self.cached_order[allow] < self.cached_order[quality['identifier']] else 5
|
||||
|
||||
# Give panelty for all lower qualities
|
||||
for q in self.qualities[self.order.index(quality.get('identifier'))+1:]:
|
||||
score[q.get('identifier')]['score'] -= 1
|
||||
|
||||
def isFinish(self, quality, profile):
|
||||
if not isinstance(profile, dict) or not profile.get('qualities'):
|
||||
return False
|
||||
|
||||
try:
|
||||
quality_order = [i for i, identifier in enumerate(profile['qualities']) if identifier == quality['identifier'] and bool(profile['3d'][i] if profile.get('3d') else 0) == bool(quality.get('is_3d', 0))][0]
|
||||
return profile['finish'][quality_order]
|
||||
except:
|
||||
return False
|
||||
|
||||
def isHigher(self, quality, compare_with, profile = None):
|
||||
if not isinstance(profile, dict) or not profile.get('qualities'):
|
||||
profile = {'qualities': self.order}
|
||||
|
||||
# Try to find quality in profile, if not found: a quality we do not want is lower than anything else
|
||||
try:
|
||||
quality_order = [i for i, identifier in enumerate(profile['qualities']) if identifier == quality['identifier'] and bool(profile['3d'][i] if profile.get('3d') else 0) == bool(quality.get('is_3d', 0))][0]
|
||||
except:
|
||||
log.debug('Quality %s not found in profile identifiers %s', (quality['identifier'] + (' 3D' if quality.get('is_3d', 0) else ''), \
|
||||
[identifier + ('3D' if (profile['3d'][i] if profile.get('3d') else 0) else '') for i, identifier in enumerate(profile['qualities'])]))
|
||||
return 'lower'
|
||||
|
||||
# Try to find compare quality in profile, if not found: anything is higher than a not wanted quality
|
||||
try:
|
||||
compare_order = [i for i, identifier in enumerate(profile['qualities']) if identifier == compare_with['identifier'] and bool(profile['3d'][i] if profile.get('3d') else 0) == bool(compare_with.get('is_3d', 0))][0]
|
||||
except:
|
||||
log.debug('Compare quality %s not found in profile identifiers %s', (compare_with['identifier'] + (' 3D' if compare_with.get('is_3d', 0) else ''), \
|
||||
[identifier + (' 3D' if (profile['3d'][i] if profile.get('3d') else 0) else '') for i, identifier in enumerate(profile['qualities'])]))
|
||||
return 'higher'
|
||||
|
||||
# Note to self: a lower number means higher quality
|
||||
if quality_order > compare_order:
|
||||
return 'lower'
|
||||
elif quality_order == compare_order:
|
||||
return 'equal'
|
||||
else:
|
||||
return 'higher'
|
||||
|
||||
def doTest(self):
|
||||
|
||||
tests = {
|
||||
'Movie Name (1999)-DVD-Rip.avi': 'dvdrip',
|
||||
'Movie Name 1999 720p Bluray.mkv': '720p',
|
||||
'Movie Name 1999 BR-Rip 720p.avi': 'brrip',
|
||||
'Movie Name 1999 720p Web Rip.avi': 'scr',
|
||||
'Movie Name 1999 Web DL.avi': 'brrip',
|
||||
'Movie.Name.1999.1080p.WEBRip.H264-Group': 'scr',
|
||||
'Movie.Name.1999.DVDRip-Group': 'dvdrip',
|
||||
'Movie.Name.1999.DVD-Rip-Group': 'dvdrip',
|
||||
'Movie.Name.1999.DVD-R-Group': 'dvdr',
|
||||
'Movie.Name.Camelie.1999.720p.BluRay.x264-Group': '720p',
|
||||
'Movie.Name.2008.German.DL.AC3.1080p.BluRay.x264-Group': '1080p',
|
||||
'Movie.Name.2004.GERMAN.AC3D.DL.1080p.BluRay.x264-Group': '1080p',
|
||||
'Movie Name (1999)-DVD-Rip.avi': {'size': 700, 'quality': 'dvdrip'},
|
||||
'Movie Name 1999 720p Bluray.mkv': {'size': 4200, 'quality': '720p'},
|
||||
'Movie Name 1999 BR-Rip 720p.avi': {'size': 1000, 'quality': 'brrip'},
|
||||
'Movie Name 1999 720p Web Rip.avi': {'size': 1200, 'quality': 'scr'},
|
||||
'Movie Name 1999 Web DL.avi': {'size': 800, 'quality': 'brrip'},
|
||||
'Movie.Name.1999.1080p.WEBRip.H264-Group': {'size': 1500, 'quality': 'scr'},
|
||||
'Movie.Name.1999.DVDRip-Group': {'size': 750, 'quality': 'dvdrip'},
|
||||
'Movie.Name.1999.DVD-Rip-Group': {'size': 700, 'quality': 'dvdrip'},
|
||||
'Movie.Name.1999.DVD-R-Group': {'size': 4500, 'quality': 'dvdr'},
|
||||
'Movie.Name.Camelie.1999.720p.BluRay.x264-Group': {'size': 5500, 'quality': '720p'},
|
||||
'Movie.Name.2008.German.DL.AC3.1080p.BluRay.x264-Group': {'size': 8500, 'extra': {'resolution_width': 1920, 'resolution_height': 1080} , 'quality': '1080p'},
|
||||
'Movie.Name.2004.GERMAN.AC3D.DL.1080p.BluRay.x264-Group': {'size': 8000, 'quality': '1080p'},
|
||||
'Movie.Name.2013.BR-Disk-Group.iso': {'size': 48000, 'quality': 'bd50'},
|
||||
'Movie.Name.2013.2D+3D.BR-Disk-Group.iso': {'size': 52000, 'quality': 'bd50', 'is_3d': True},
|
||||
'Movie.Rising.Name.Girl.2011.NTSC.DVD9-GroupDVD': {'size': 7200, 'quality': 'dvdr'},
|
||||
'Movie Name (2013) 2D + 3D': {'size': 49000, 'quality': 'bd50', 'is_3d': True},
|
||||
'Movie Monuments 2013 BrRip 1080p': {'size': 1800, 'quality': 'brrip'},
|
||||
'Movie Monuments 2013 BrRip 720p': {'size': 1300, 'quality': 'brrip'},
|
||||
}
|
||||
|
||||
correct = 0
|
||||
for name in tests:
|
||||
success = self.guess([name]).get('identifier') == tests[name]
|
||||
test_quality = self.guess(files = [name], extra = tests[name].get('extra', None), size = tests[name].get('size', None)) or {}
|
||||
success = test_quality.get('identifier') == tests[name]['quality'] and test_quality.get('is_3d') == tests[name].get('is_3d', False)
|
||||
if not success:
|
||||
log.error('%s failed check, thinks it\'s %s', (name, self.guess([name]).get('identifier')))
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@ import os
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from CodernityDB.database import RecordDeleted
|
||||
from couchpotato import md5, get_db
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent, addEvent
|
||||
from couchpotato.core.helpers.encoding import ss, toUnicode
|
||||
from couchpotato.core.helpers.encoding import ss, toUnicode, sp
|
||||
from couchpotato.core.helpers.variable import getTitle
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
@@ -58,7 +59,7 @@ class Release(Plugin):
|
||||
|
||||
# Clean releases that didn't have activity in the last week
|
||||
addEvent('app.load', self.cleanDone)
|
||||
fireEvent('schedule.interval', 'movie.clean_releases', self.cleanDone, hours = 4)
|
||||
fireEvent('schedule.interval', 'movie.clean_releases', self.cleanDone, hours = 12)
|
||||
|
||||
def cleanDone(self):
|
||||
log.debug('Removing releases from dashboard')
|
||||
@@ -68,6 +69,27 @@ class Release(Plugin):
|
||||
|
||||
db = get_db()
|
||||
|
||||
# Get (and remove) parentless releases
|
||||
releases = db.all('release', with_doc = True)
|
||||
media_exist = []
|
||||
for release in releases:
|
||||
if release.get('key') in media_exist:
|
||||
continue
|
||||
|
||||
try:
|
||||
db.get('id', release.get('key'))
|
||||
media_exist.append(release.get('key'))
|
||||
except RecordDeleted:
|
||||
db.delete(release['doc'])
|
||||
log.debug('Deleted orphaned release: %s', release['doc'])
|
||||
except:
|
||||
log.debug('Failed cleaning up orphaned releases: %s', traceback.format_exc())
|
||||
|
||||
del media_exist
|
||||
|
||||
# Reindex statuses
|
||||
db.reindex_index('media_status')
|
||||
|
||||
# get movies last_edit more than a week ago
|
||||
medias = fireEvent('media.with_status', 'done', single = True)
|
||||
|
||||
@@ -107,6 +129,7 @@ class Release(Plugin):
|
||||
'media_id': media['_id'],
|
||||
'identifier': release_identifier,
|
||||
'quality': group['meta_data']['quality'].get('identifier'),
|
||||
'is_3d': group['meta_data']['quality'].get('is_3d', 0),
|
||||
'last_edit': int(time.time()),
|
||||
'status': 'done'
|
||||
}
|
||||
@@ -157,15 +180,20 @@ class Release(Plugin):
|
||||
try:
|
||||
db = get_db()
|
||||
rel = db.get('id', release_id)
|
||||
raw_files = rel.get('files')
|
||||
|
||||
if len(rel.get('files')) == 0:
|
||||
if len(raw_files) == 0:
|
||||
self.delete(rel['_id'])
|
||||
else:
|
||||
|
||||
files = []
|
||||
for release_file in rel.get('files'):
|
||||
if os.path.isfile(ss(release_file['path'])):
|
||||
files.append(release_file)
|
||||
files = {}
|
||||
for file_type in raw_files:
|
||||
|
||||
for release_file in raw_files.get(file_type, []):
|
||||
if os.path.isfile(sp(release_file)):
|
||||
if file_type not in files:
|
||||
files[file_type] = []
|
||||
files[file_type].append(release_file)
|
||||
|
||||
rel['files'] = files
|
||||
db.update(rel)
|
||||
@@ -315,10 +343,12 @@ class Release(Plugin):
|
||||
|
||||
def tryDownloadResult(self, results, media, quality_custom, manual = False):
|
||||
|
||||
wait_for = False
|
||||
let_through = False
|
||||
filtered_results = []
|
||||
|
||||
# If a single release comes through the "wait for", let through all
|
||||
for rel in results:
|
||||
if not quality_custom.get('finish', False) and quality_custom.get('wait_for', 0) > 0 and rel.get('age') <= quality_custom.get('wait_for', 0):
|
||||
log.info('Ignored, waiting %s days: %s', (quality_custom.get('wait_for'), rel['name']))
|
||||
continue
|
||||
|
||||
if rel['status'] in ['ignored', 'failed']:
|
||||
log.info('Ignored: %s', rel['name'])
|
||||
@@ -328,13 +358,30 @@ class Release(Plugin):
|
||||
log.info('Ignored, score to low: %s', rel['name'])
|
||||
continue
|
||||
|
||||
rel['wait_for'] = False
|
||||
if quality_custom.get('index') != 0 and quality_custom.get('wait_for', 0) > 0 and rel.get('age') <= quality_custom.get('wait_for', 0):
|
||||
rel['wait_for'] = True
|
||||
else:
|
||||
let_through = True
|
||||
|
||||
filtered_results.append(rel)
|
||||
|
||||
# Loop through filtered results
|
||||
for rel in filtered_results:
|
||||
|
||||
# Only wait if not a single release is old enough
|
||||
if rel.get('wait_for') and not let_through:
|
||||
log.info('Ignored, waiting %s days: %s', (quality_custom.get('wait_for') - rel.get('age'), rel['name']))
|
||||
wait_for = True
|
||||
continue
|
||||
|
||||
downloaded = fireEvent('release.download', data = rel, media = media, manual = manual, single = True)
|
||||
if downloaded is True:
|
||||
return True
|
||||
elif downloaded != 'try_next':
|
||||
break
|
||||
|
||||
return False
|
||||
return wait_for
|
||||
|
||||
def createFromSearch(self, search_results, media, quality):
|
||||
|
||||
@@ -406,7 +453,7 @@ class Release(Plugin):
|
||||
rel = db.get('id', release_id)
|
||||
if rel and rel.get('status') != status:
|
||||
|
||||
release_name = rel.get('name')
|
||||
release_name = rel['info'].get('name')
|
||||
if rel.get('files'):
|
||||
for file_type in rel.get('files', {}):
|
||||
if file_type == 'movie':
|
||||
|
||||
@@ -112,16 +112,13 @@ class Renamer(Plugin):
|
||||
return
|
||||
|
||||
if not base_folder:
|
||||
base_folder = self.conf('from')
|
||||
base_folder = sp(self.conf('from'))
|
||||
|
||||
from_folder = sp(self.conf('from'))
|
||||
to_folder = sp(self.conf('to'))
|
||||
|
||||
# Get media folder to process
|
||||
media_folder = release_download.get('folder')
|
||||
|
||||
# Quality order for calculation quality priority
|
||||
quality_order = fireEvent('quality.order', single = True)
|
||||
media_folder = sp(release_download.get('folder'))
|
||||
|
||||
# Get all folders that should not be processed
|
||||
no_process = [to_folder]
|
||||
@@ -149,9 +146,9 @@ class Renamer(Plugin):
|
||||
|
||||
# Update to the from folder
|
||||
if len(release_download.get('files', [])) == 1:
|
||||
new_media_folder = from_folder
|
||||
new_media_folder = sp(from_folder)
|
||||
else:
|
||||
new_media_folder = os.path.join(from_folder, os.path.basename(media_folder))
|
||||
new_media_folder = sp(os.path.join(from_folder, os.path.basename(media_folder)))
|
||||
|
||||
if not os.path.isdir(new_media_folder):
|
||||
log.error('The provided media folder %s does not exist and could also not be found in the \'from\' folder.', media_folder)
|
||||
@@ -290,8 +287,10 @@ class Renamer(Plugin):
|
||||
|
||||
# Put 'The' at the end
|
||||
name_the = movie_name
|
||||
if movie_name[:4].lower() == 'the ':
|
||||
name_the = movie_name[4:] + ', The'
|
||||
for prefix in ['the ', 'an ', 'a ']:
|
||||
if prefix == movie_name[:len(prefix)].lower():
|
||||
name_the = movie_name[len(prefix):] + ', ' + prefix.strip().capitalize()
|
||||
break
|
||||
|
||||
replacements = {
|
||||
'ext': 'mkv',
|
||||
@@ -312,9 +311,15 @@ class Renamer(Plugin):
|
||||
'cd': '',
|
||||
'cd_nr': '',
|
||||
'mpaa': media['info'].get('mpaa', ''),
|
||||
'mpaa_only': media['info'].get('mpaa', ''),
|
||||
'category': category_label,
|
||||
'3d': '3D' if group['meta_data']['quality'].get('is_3d', 0) else '',
|
||||
'3d_type': group['meta_data'].get('3d_type'),
|
||||
}
|
||||
|
||||
if replacements['mpaa_only'] not in ('G', 'PG', 'PG-13', 'R', 'NC-17'):
|
||||
replacements['mpaa_only'] = 'Not Rated'
|
||||
|
||||
for file_type in group['files']:
|
||||
|
||||
# Move nfo depending on settings
|
||||
@@ -410,8 +415,12 @@ class Renamer(Plugin):
|
||||
|
||||
# Don't add language if multiple languages in 1 subtitle file
|
||||
if len(sub_langs) == 1:
|
||||
sub_name = sub_name.replace(replacements['ext'], '%s.%s' % (sub_langs[0], replacements['ext']))
|
||||
rename_files[current_file] = os.path.join(destination, final_folder_name, sub_name)
|
||||
sub_suffix = '%s.%s' % (sub_langs[0], replacements['ext'])
|
||||
|
||||
# Don't add language to subtitle file it it's already there
|
||||
if not sub_name.endswith(sub_suffix):
|
||||
sub_name = sub_name.replace(replacements['ext'], sub_suffix)
|
||||
rename_files[current_file] = os.path.join(destination, final_folder_name, sub_name)
|
||||
|
||||
rename_files = mergeDicts(rename_files, rename_extras)
|
||||
|
||||
@@ -438,17 +447,15 @@ class Renamer(Plugin):
|
||||
remove_leftovers = True
|
||||
|
||||
# Mark movie "done" once it's found the quality with the finish check
|
||||
profile = None
|
||||
try:
|
||||
if media.get('status') == 'active' and media.get('profile_id'):
|
||||
profile = db.get('id', media['profile_id'])
|
||||
if group['meta_data']['quality']['identifier'] in profile.get('qualities', []):
|
||||
nr = profile['qualities'].index(group['meta_data']['quality']['identifier'])
|
||||
finish = profile['finish'][nr]
|
||||
if finish:
|
||||
mdia = db.get('id', media['_id'])
|
||||
mdia['status'] = 'done'
|
||||
mdia['last_edit'] = int(time.time())
|
||||
db.update(mdia)
|
||||
if fireEvent('quality.isfinish', group['meta_data']['quality'], profile, single = True):
|
||||
mdia = db.get('id', media['_id'])
|
||||
mdia['status'] = 'done'
|
||||
mdia['last_edit'] = int(time.time())
|
||||
db.update(mdia)
|
||||
|
||||
except Exception as e:
|
||||
log.error('Failed marking movie finished: %s', (traceback.format_exc()))
|
||||
@@ -459,18 +466,19 @@ class Renamer(Plugin):
|
||||
# When a release already exists
|
||||
if release.get('status') == 'done':
|
||||
|
||||
release_order = quality_order.index(release['quality'])
|
||||
group_quality_order = quality_order.index(group['meta_data']['quality']['identifier'])
|
||||
# This is where CP removes older, lesser quality releases or releases that are not wanted anymore
|
||||
is_higher = fireEvent('quality.ishigher', \
|
||||
group['meta_data']['quality'], {'identifier': release['quality'], 'is_3d': release.get('is_3d', 0)}, profile, single = True)
|
||||
|
||||
# This is where CP removes older, lesser quality releases
|
||||
if release_order > group_quality_order:
|
||||
log.info('Removing lesser quality %s for %s.', (media_title, release.get('quality')))
|
||||
if is_higher == 'higher':
|
||||
log.info('Removing lesser or not wanted quality %s for %s.', (media_title, release.get('quality')))
|
||||
for file_type in release.get('files', {}):
|
||||
for release_file in release['files'][file_type]:
|
||||
remove_files.append(release_file)
|
||||
remove_releases.append(release)
|
||||
|
||||
# Same quality, but still downloaded, so maybe repack/proper/unrated/directors cut etc
|
||||
elif release_order == group_quality_order:
|
||||
elif is_higher == 'equal':
|
||||
log.info('Same quality release already exists for %s, with quality %s. Assuming repack.', (media_title, release.get('quality')))
|
||||
for file_type in release.get('files', {}):
|
||||
for release_file in release['files'][file_type]:
|
||||
@@ -652,11 +660,11 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
elif isinstance(release_download, dict):
|
||||
# Tag download_files if they are known
|
||||
if release_download.get('files', []):
|
||||
tag_files = release_download.get('files', [])
|
||||
tag_files = [filename for filename in release_download.get('files', []) if os.path.exists(filename)]
|
||||
|
||||
# Tag all files in release folder
|
||||
elif release_download['folder']:
|
||||
for root, folders, names in scandir.walk(release_download['folder']):
|
||||
for root, folders, names in scandir.walk(sp(release_download['folder'])):
|
||||
tag_files.extend([os.path.join(root, name) for name in names])
|
||||
|
||||
for filename in tag_files:
|
||||
@@ -680,13 +688,13 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
if isinstance(group, dict):
|
||||
tag_files = [sorted(list(group['files']['movie']))[0]]
|
||||
|
||||
folder = group['parentdir']
|
||||
folder = sp(group['parentdir'])
|
||||
if not group.get('dirname') or not os.path.isdir(folder):
|
||||
return False
|
||||
|
||||
elif isinstance(release_download, dict):
|
||||
|
||||
folder = release_download['folder']
|
||||
folder = sp(release_download['folder'])
|
||||
if not os.path.isdir(folder):
|
||||
return False
|
||||
|
||||
@@ -720,7 +728,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
if not release_download:
|
||||
return False
|
||||
|
||||
folder = release_download['folder']
|
||||
folder = sp(release_download['folder'])
|
||||
if not os.path.isdir(folder):
|
||||
return False
|
||||
|
||||
@@ -749,7 +757,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
return False
|
||||
|
||||
def moveFile(self, old, dest, forcemove = False):
|
||||
dest = ss(dest)
|
||||
dest = sp(dest)
|
||||
try:
|
||||
if forcemove or self.conf('file_action') not in ['copy', 'link']:
|
||||
try:
|
||||
@@ -822,7 +830,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
def replaceDoubles(self, string):
|
||||
|
||||
replaces = [
|
||||
('\.+', '.'), ('_+', '_'), ('-+', '-'), ('\s+', ' '),
|
||||
('\.+', '.'), ('_+', '_'), ('-+', '-'), ('\s+', ' '), (' \\\\', '\\\\'), (' /', '/'),
|
||||
('(\s\.)+', '.'), ('(-\.)+', '.'), ('(\s-)+', '-'),
|
||||
]
|
||||
|
||||
@@ -1054,6 +1062,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
release_download.update({
|
||||
'imdb_id': getIdentifier(media),
|
||||
'quality': rls['quality'],
|
||||
'is_3d': rls['is_3d'],
|
||||
'protocol': rls.get('info', {}).get('protocol') or rls.get('info', {}).get('type'),
|
||||
'release_id': rls['_id'],
|
||||
})
|
||||
@@ -1193,6 +1202,8 @@ rename_options = {
|
||||
'first': 'First letter (M)',
|
||||
'quality': 'Quality (720p)',
|
||||
'quality_type': '(HD) or (SD)',
|
||||
'3d': '3D',
|
||||
'3d_type': '3D Type (Full SBS)',
|
||||
'video': 'Video (x264)',
|
||||
'audio': 'Audio (DTS)',
|
||||
'group': 'Releasegroup name',
|
||||
@@ -1205,7 +1216,8 @@ rename_options = {
|
||||
'imdb_id': 'IMDB id (tt0123456)',
|
||||
'cd': 'CD number (cd1)',
|
||||
'cd_nr': 'Just the cd nr. (1)',
|
||||
'mpaa': 'MPAA Rating',
|
||||
'mpaa': 'MPAA or other certification',
|
||||
'mpaa_only': 'MPAA only certification (G|PG|PG-13|R|NC-17|Not Rated)',
|
||||
'category': 'Category label',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import traceback
|
||||
|
||||
from couchpotato import get_db
|
||||
from couchpotato.core.event import fireEvent, addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode, simplifyString, sp
|
||||
from couchpotato.core.helpers.encoding import toUnicode, simplifyString, sp, ss
|
||||
from couchpotato.core.helpers.variable import getExt, getImdb, tryInt, \
|
||||
splitString, getIdentifier
|
||||
from couchpotato.core.logger import CPLog
|
||||
@@ -40,6 +40,17 @@ class Scanner(Plugin):
|
||||
'trailer': ['mov', 'mp4', 'flv']
|
||||
}
|
||||
|
||||
threed_types = {
|
||||
'Half SBS': [('half', 'sbs'), ('h', 'sbs'), 'hsbs'],
|
||||
'Full SBS': [('full', 'sbs'), ('f', 'sbs'), 'fsbs'],
|
||||
'SBS': ['sbs'],
|
||||
'Half OU': [('half', 'ou'), ('h', 'ou'), 'hou'],
|
||||
'Full OU': [('full', 'ou'), ('h', 'ou'), 'fou'],
|
||||
'OU': ['ou'],
|
||||
'Frame Packed': ['mvc', ('complete', 'bluray')],
|
||||
'3D': ['3d']
|
||||
}
|
||||
|
||||
file_types = {
|
||||
'subtitle': ('subtitle', 'subtitle'),
|
||||
'subtitle_extra': ('subtitle', 'subtitle_extra'),
|
||||
@@ -60,33 +71,43 @@ class Scanner(Plugin):
|
||||
}
|
||||
|
||||
codecs = {
|
||||
'audio': ['dts', 'ac3', 'ac3d', 'mp3'],
|
||||
'video': ['x264', 'h264', 'divx', 'xvid']
|
||||
'audio': ['DTS', 'AC3', 'AC3D', 'MP3'],
|
||||
'video': ['x264', 'H264', 'DivX', 'Xvid']
|
||||
}
|
||||
|
||||
resolutions = {
|
||||
'1080p': {'resolution_width': 1920, 'resolution_height': 1080, 'aspect': 1.78},
|
||||
'1080i': {'resolution_width': 1920, 'resolution_height': 1080, 'aspect': 1.78},
|
||||
'720p': {'resolution_width': 1280, 'resolution_height': 720, 'aspect': 1.78},
|
||||
'720i': {'resolution_width': 1280, 'resolution_height': 720, 'aspect': 1.78},
|
||||
'480p': {'resolution_width': 640, 'resolution_height': 480, 'aspect': 1.33},
|
||||
'480i': {'resolution_width': 640, 'resolution_height': 480, 'aspect': 1.33},
|
||||
'default': {'resolution_width': 0, 'resolution_height': 0, 'aspect': 1},
|
||||
}
|
||||
|
||||
audio_codec_map = {
|
||||
0x2000: 'ac3',
|
||||
0x2001: 'dts',
|
||||
0x0055: 'mp3',
|
||||
0x0050: 'mp2',
|
||||
0x0001: 'pcm',
|
||||
0x003: 'pcm',
|
||||
0x77a1: 'tta1',
|
||||
0x5756: 'wav',
|
||||
0x6750: 'vorbis',
|
||||
0xF1AC: 'flac',
|
||||
0x00ff: 'aac',
|
||||
0x2000: 'AC3',
|
||||
0x2001: 'DTS',
|
||||
0x0055: 'MP3',
|
||||
0x0050: 'MP2',
|
||||
0x0001: 'PCM',
|
||||
0x003: 'WAV',
|
||||
0x77a1: 'TTA1',
|
||||
0x5756: 'WAV',
|
||||
0x6750: 'Vorbis',
|
||||
0xF1AC: 'FLAC',
|
||||
0x00ff: 'AAC',
|
||||
}
|
||||
|
||||
source_media = {
|
||||
'bluray': ['bluray', 'blu-ray', 'brrip', 'br-rip'],
|
||||
'hddvd': ['hddvd', 'hd-dvd'],
|
||||
'dvd': ['dvd'],
|
||||
'hdtv': ['hdtv']
|
||||
'Blu-ray': ['bluray', 'blu-ray', 'brrip', 'br-rip'],
|
||||
'HD DVD': ['hddvd', 'hd-dvd'],
|
||||
'DVD': ['dvd'],
|
||||
'HDTV': ['hdtv']
|
||||
}
|
||||
|
||||
clean = '[ _\,\.\(\)\[\]\-]?(3d|hsbs|sbs|extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|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]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
|
||||
clean = '([ _\,\.\(\)\[\]\-]|^)(3d|hsbs|sbs|ou|extended.cut|directors.cut|french|fr|swedisch|sw|danish|dutch|nl|swesub|subs|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip' \
|
||||
'|hdtvrip|webdl|web.dl|webrip|web.rip|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|hc|\[.*\])(?=[ _\,\.\(\)\[\]\-]|$)'
|
||||
multipart_regex = [
|
||||
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
||||
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
||||
@@ -130,7 +151,7 @@ class Scanner(Plugin):
|
||||
try:
|
||||
files = []
|
||||
for root, dirs, walk_files in scandir.walk(folder, followlinks=True):
|
||||
files.extend([sp(os.path.join(root, filename)) for filename in walk_files])
|
||||
files.extend([sp(os.path.join(sp(root), ss(filename))) for filename in walk_files])
|
||||
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
@@ -164,7 +185,7 @@ class Scanner(Plugin):
|
||||
identifiers = [identifier]
|
||||
|
||||
# Identifier with quality
|
||||
quality = fireEvent('quality.guess', [file_path], single = True) if not is_dvd_file else {'identifier':'dvdr'}
|
||||
quality = fireEvent('quality.guess', files = [file_path], size = self.getFileSize(file_path), single = True) if not is_dvd_file else {'identifier':'dvdr'}
|
||||
if quality:
|
||||
identifier_with_quality = '%s %s' % (identifier, quality.get('identifier', ''))
|
||||
identifiers = [identifier_with_quality, identifier]
|
||||
@@ -431,28 +452,39 @@ class Scanner(Plugin):
|
||||
for cur_file in files:
|
||||
if not self.filesizeBetween(cur_file, self.file_sizes['movie']): continue # Ignore smaller files
|
||||
|
||||
meta = self.getMeta(cur_file)
|
||||
if not data.get('audio'): # Only get metadata from first media file
|
||||
meta = self.getMeta(cur_file)
|
||||
|
||||
try:
|
||||
data['video'] = meta.get('video', self.getCodec(cur_file, self.codecs['video']))
|
||||
data['audio'] = meta.get('audio', self.getCodec(cur_file, self.codecs['audio']))
|
||||
data['resolution_width'] = meta.get('resolution_width', 720)
|
||||
data['resolution_height'] = meta.get('resolution_height', 480)
|
||||
data['audio_channels'] = meta.get('audio_channels', 2.0)
|
||||
data['aspect'] = round(float(meta.get('resolution_width', 720)) / meta.get('resolution_height', 480), 2)
|
||||
except:
|
||||
log.debug('Error parsing metadata: %s %s', (cur_file, traceback.format_exc()))
|
||||
pass
|
||||
try:
|
||||
data['video'] = meta.get('video', self.getCodec(cur_file, self.codecs['video']))
|
||||
data['audio'] = meta.get('audio', self.getCodec(cur_file, self.codecs['audio']))
|
||||
data['audio_channels'] = meta.get('audio_channels', 2.0)
|
||||
if meta.get('resolution_width'):
|
||||
data['resolution_width'] = meta.get('resolution_width')
|
||||
data['resolution_height'] = meta.get('resolution_height')
|
||||
data['aspect'] = round(float(meta.get('resolution_width')) / meta.get('resolution_height', 1), 2)
|
||||
else:
|
||||
data.update(self.getResolution(cur_file))
|
||||
except:
|
||||
log.debug('Error parsing metadata: %s %s', (cur_file, traceback.format_exc()))
|
||||
pass
|
||||
|
||||
if data.get('audio'): break
|
||||
data['size'] = data.get('size', 0) + self.getFileSize(cur_file)
|
||||
|
||||
# Use the quality guess first, if that failes use the quality we wanted to download
|
||||
data['quality'] = None
|
||||
quality = fireEvent('quality.guess', size = data['size'], files = files, extra = data, single = True)
|
||||
|
||||
# Use the quality that we snatched but check if it matches our guess
|
||||
if release_download and release_download.get('quality'):
|
||||
data['quality'] = fireEvent('quality.single', release_download.get('quality'), single = True)
|
||||
data['quality']['is_3d'] = release_download.get('is_3d', 0)
|
||||
if data['quality']['identifier'] != quality['identifier']:
|
||||
log.info('Different quality snatched than detected for %s: %s vs. %s. Assuming snatched quality is correct.', (files[0], data['quality']['identifier'], quality['identifier']))
|
||||
if data['quality']['is_3d'] != quality['is_3d']:
|
||||
log.info('Different 3d snatched than detected for %s: %s vs. %s. Assuming snatched 3d is correct.', (files[0], data['quality']['is_3d'], quality['is_3d']))
|
||||
|
||||
if not data['quality']:
|
||||
data['quality'] = fireEvent('quality.guess', files = files, extra = data, single = True)
|
||||
data['quality'] = quality
|
||||
|
||||
if not data['quality']:
|
||||
data['quality'] = fireEvent('quality.single', 'dvdr' if group['is_dvd'] else 'dvdrip', single = True)
|
||||
@@ -462,16 +494,32 @@ class Scanner(Plugin):
|
||||
filename = re.sub('(.cp\(tt[0-9{7}]+\))', '', files[0])
|
||||
data['group'] = self.getGroup(filename[len(folder):])
|
||||
data['source'] = self.getSourceMedia(filename)
|
||||
|
||||
if data['quality'].get('is_3d', 0):
|
||||
data['3d_type'] = self.get3dType(filename)
|
||||
return data
|
||||
|
||||
def get3dType(self, filename):
|
||||
filename = ss(filename)
|
||||
|
||||
words = re.split('\W+', filename.lower())
|
||||
|
||||
for key in self.threed_types:
|
||||
tags = self.threed_types.get(key, [])
|
||||
|
||||
for tag in tags:
|
||||
if (isinstance(tag, tuple) and '.'.join(tag) in '.'.join(words)) or (isinstance(tag, (str, unicode)) and ss(tag.lower()) in words):
|
||||
log.debug('Found %s in %s', (tag, filename))
|
||||
return key
|
||||
|
||||
return ''
|
||||
|
||||
def getMeta(self, filename):
|
||||
|
||||
try:
|
||||
p = enzyme.parse(filename)
|
||||
|
||||
# Video codec
|
||||
vc = ('h264' if p.video[0].codec == 'AVC1' else p.video[0].codec).lower()
|
||||
vc = ('H264' if p.video[0].codec == 'AVC1' else p.video[0].codec)
|
||||
|
||||
# Audio codec
|
||||
ac = p.audio[0].codec
|
||||
@@ -708,19 +756,26 @@ class Scanner(Plugin):
|
||||
if not file_size: file_size = []
|
||||
|
||||
try:
|
||||
return (file_size.get('min', 0) * 1048576) < os.path.getsize(file) < (file_size.get('max', 100000) * 1048576)
|
||||
return file_size.get('min', 0) < self.getFileSize(file) < file_size.get('max', 100000)
|
||||
except:
|
||||
log.error('Couldn\'t get filesize of %s.', file)
|
||||
|
||||
return False
|
||||
|
||||
def createStringIdentifier(self, file_path, folder = '', exclude_filename = False):
|
||||
def getFileSize(self, file):
|
||||
try:
|
||||
return os.path.getsize(file) / 1024 / 1024
|
||||
except:
|
||||
return None
|
||||
|
||||
year = self.findYear(file_path)
|
||||
def createStringIdentifier(self, file_path, folder = '', exclude_filename = False):
|
||||
|
||||
identifier = file_path.replace(folder, '').lstrip(os.path.sep) # root folder
|
||||
identifier = os.path.splitext(identifier)[0] # ext
|
||||
|
||||
# Make sure the identifier is lower case as all regex is with lower case tags
|
||||
identifier = identifier.lower()
|
||||
|
||||
try:
|
||||
path_split = splitString(identifier, os.path.sep)
|
||||
identifier = path_split[-2] if len(path_split) > 1 and len(path_split[-2]) > len(path_split[-1]) else path_split[-1] # Only get filename
|
||||
@@ -735,8 +790,13 @@ class Scanner(Plugin):
|
||||
# remove cptag
|
||||
identifier = self.removeCPTag(identifier)
|
||||
|
||||
# groups, release tags, scenename cleaner, regex isn't correct
|
||||
identifier = re.sub(self.clean, '::', simplifyString(identifier)).strip(':')
|
||||
# simplify the string
|
||||
identifier = simplifyString(identifier)
|
||||
|
||||
year = self.findYear(file_path)
|
||||
|
||||
# groups, release tags, scenename cleaner
|
||||
identifier = re.sub(self.clean, '::', identifier).strip(':')
|
||||
|
||||
# Year
|
||||
if year and identifier[:4] != year:
|
||||
@@ -785,6 +845,14 @@ class Scanner(Plugin):
|
||||
except:
|
||||
return ''
|
||||
|
||||
def getResolution(self, filename):
|
||||
try:
|
||||
for key in self.resolutions:
|
||||
if key in filename.lower() and key != 'default':
|
||||
return self.resolutions[key]
|
||||
except:
|
||||
return self.resolutions['default']
|
||||
|
||||
def getGroup(self, file):
|
||||
try:
|
||||
match = re.findall('\-([A-Z0-9]+)[\.\/]', file, re.I)
|
||||
|
||||
@@ -32,7 +32,7 @@ class Subtitle(Plugin):
|
||||
|
||||
for lang in self.getLanguages():
|
||||
if lang not in available_languages:
|
||||
download = subliminal.download_subtitles(files, multi = True, force = False, languages = [lang], services = self.services, cache_dir = Env.get('cache_dir'))
|
||||
download = subliminal.download_subtitles(files, multi = True, force = self.conf('force'), languages = [lang], services = self.services, cache_dir = Env.get('cache_dir'))
|
||||
for subtitle in download:
|
||||
downloaded.extend(download[subtitle])
|
||||
|
||||
@@ -72,6 +72,14 @@ config = [{
|
||||
'name': 'languages',
|
||||
'description': ('Comma separated, 2 letter country code.', 'Example: en, nl. See the codes at <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">on Wikipedia</a>'),
|
||||
},
|
||||
{
|
||||
'advanced': True,
|
||||
'name': 'force',
|
||||
'label': 'Force',
|
||||
'description': ('Force download all languages (including embedded).', 'This will also <strong>overwrite</strong> all existing subtitles.'),
|
||||
'default': False,
|
||||
'type': 'bool',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -2,6 +2,7 @@ Page.Userscript = new Class({
|
||||
|
||||
Extends: PageBase,
|
||||
|
||||
order: 80,
|
||||
name: 'userscript',
|
||||
has_tab: false,
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ Page.Wizard = new Class({
|
||||
|
||||
Extends: Page.Settings,
|
||||
|
||||
order: 70,
|
||||
name: 'wizard',
|
||||
has_tab: false,
|
||||
wizard_only: true,
|
||||
|
||||
Reference in new issue
Block a user