Merge branch 'refs/heads/develop' into redesign

Conflicts:
	couchpotato/static/scripts/couchpotato.js
This commit is contained in:
Ruud committed 2014-05-11 20:25:15 +02:00
commit 58acd53a9a
65 files changed
+1204 -388

No files matched your search

+2 -2
View File
@@ -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'))
+14 -1
View File
@@ -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
+51 -11
View File
@@ -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):
+135 -13
View File
@@ -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;
}
+231 -30
View File
@@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.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 );
}
});
+9 -3
View File
@@ -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
+2 -2
View File
@@ -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
}
},
+95 -26
View File
@@ -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')))
+59 -12
View File
@@ -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':
+45 -33
View File
@@ -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',
},
}
+111 -43
View File
@@ -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)
+9 -1
View File
@@ -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,