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
+1
-1
@@ -29,7 +29,7 @@ class Loader(object):
|
|||||||
|
|
||||||
# Get options via arg
|
# Get options via arg
|
||||||
from couchpotato.runner import getOptions
|
from couchpotato.runner import getOptions
|
||||||
self.options = getOptions(base_path, sys.argv[1:])
|
self.options = getOptions(sys.argv[1:])
|
||||||
|
|
||||||
# Load settings
|
# Load settings
|
||||||
settings = Env.get('settings')
|
settings = Env.get('settings')
|
||||||
|
|||||||
@@ -90,7 +90,11 @@ class Core(Plugin):
|
|||||||
|
|
||||||
def shutdown():
|
def shutdown():
|
||||||
self.initShutdown()
|
self.initShutdown()
|
||||||
IOLoop.current().add_callback(shutdown)
|
|
||||||
|
if IOLoop.current()._closing:
|
||||||
|
shutdown()
|
||||||
|
else:
|
||||||
|
IOLoop.current().add_callback(shutdown)
|
||||||
|
|
||||||
return 'shutdown'
|
return 'shutdown'
|
||||||
|
|
||||||
@@ -139,7 +143,8 @@ class Core(Plugin):
|
|||||||
log.debug('Safe to shutdown/restart')
|
log.debug('Safe to shutdown/restart')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
IOLoop.current().stop()
|
if not IOLoop.current()._closing:
|
||||||
|
IOLoop.current().stop()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from threading import RLock
|
|||||||
|
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import ss
|
from couchpotato.core.helpers.encoding import ss, sp
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -182,7 +182,7 @@ class BaseUpdater(Plugin):
|
|||||||
|
|
||||||
def deletePyc(self, only_excess = True, show_logs = True):
|
def deletePyc(self, only_excess = True, show_logs = True):
|
||||||
|
|
||||||
for root, dirs, files in scandir.walk(ss(Env.get('app_dir'))):
|
for root, dirs, files in scandir.walk(Env.get('app_dir')):
|
||||||
|
|
||||||
pyc_files = filter(lambda filename: filename.endswith('.pyc'), files)
|
pyc_files = filter(lambda filename: filename.endswith('.pyc'), files)
|
||||||
py_files = set(filter(lambda filename: filename.endswith('.py'), files))
|
py_files = set(filter(lambda filename: filename.endswith('.py'), files))
|
||||||
@@ -322,8 +322,9 @@ class SourceUpdater(BaseUpdater):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def replaceWith(self, path):
|
def replaceWith(self, path):
|
||||||
app_dir = ss(Env.get('app_dir'))
|
path = sp(path)
|
||||||
data_dir = ss(Env.get('data_dir'))
|
app_dir = Env.get('app_dir')
|
||||||
|
data_dir = Env.get('data_dir')
|
||||||
|
|
||||||
# Get list of files we want to overwrite
|
# Get list of files we want to overwrite
|
||||||
self.deletePyc()
|
self.deletePyc()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class Database(object):
|
|||||||
|
|
||||||
addEvent('database.setup_index', self.setupIndex)
|
addEvent('database.setup_index', self.setupIndex)
|
||||||
addEvent('app.migrate', self.migrate)
|
addEvent('app.migrate', self.migrate)
|
||||||
|
addEvent('app.after_shutdown', self.close)
|
||||||
|
|
||||||
def getDB(self):
|
def getDB(self):
|
||||||
|
|
||||||
@@ -37,6 +38,9 @@ class Database(object):
|
|||||||
|
|
||||||
return self.db
|
return self.db
|
||||||
|
|
||||||
|
def close(self, **kwargs):
|
||||||
|
self.getDB().close()
|
||||||
|
|
||||||
def setupIndex(self, index_name, klass):
|
def setupIndex(self, index_name, klass):
|
||||||
|
|
||||||
self.indexes.append(index_name)
|
self.indexes.append(index_name)
|
||||||
@@ -412,7 +416,10 @@ class Database(object):
|
|||||||
empty_info = True
|
empty_info = True
|
||||||
rel['info'] = {}
|
rel['info'] = {}
|
||||||
|
|
||||||
quality = quality_link[rel.get('quality_id')]
|
quality = quality_link.get(rel.get('quality_id'))
|
||||||
|
if not quality:
|
||||||
|
continue
|
||||||
|
|
||||||
release_status = statuses.get(rel.get('status_id')).get('identifier')
|
release_status = statuses.get(rel.get('status_id')).get('identifier')
|
||||||
|
|
||||||
if rel['info'].get('download_id'):
|
if rel['info'].get('download_id'):
|
||||||
|
|||||||
@@ -154,19 +154,13 @@ class rTorrent(DownloaderBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def getTorrentStatus(self, torrent):
|
def getTorrentStatus(self, torrent):
|
||||||
if torrent.hashing or torrent.hash_checking or torrent.message:
|
|
||||||
return 'busy'
|
|
||||||
|
|
||||||
if not torrent.complete:
|
if not torrent.complete:
|
||||||
return 'busy'
|
return 'busy'
|
||||||
|
|
||||||
if not torrent.open:
|
if torrent.open:
|
||||||
return 'completed'
|
|
||||||
|
|
||||||
if torrent.state and torrent.active:
|
|
||||||
return 'seeding'
|
return 'seeding'
|
||||||
|
|
||||||
return 'busy'
|
return 'completed'
|
||||||
|
|
||||||
def getAllDownloadStatus(self, ids):
|
def getAllDownloadStatus(self, ids):
|
||||||
log.debug('Checking rTorrent download status.')
|
log.debug('Checking rTorrent download status.')
|
||||||
@@ -244,7 +238,7 @@ class rTorrent(DownloaderBase):
|
|||||||
if torrent.is_multi_file() and torrent.directory.endswith(torrent.name):
|
if torrent.is_multi_file() and torrent.directory.endswith(torrent.name):
|
||||||
# Remove empty directories bottom up
|
# Remove empty directories bottom up
|
||||||
try:
|
try:
|
||||||
for path, _, _ in scandir.walk(torrent.directory, topdown = False):
|
for path, _, _ in scandir.walk(sp(torrent.directory), topdown = False):
|
||||||
os.rmdir(path)
|
os.rmdir(path)
|
||||||
except OSError:
|
except OSError:
|
||||||
log.info('Directory "%s" contains extra files, unable to remove', torrent.directory)
|
log.info('Directory "%s" contains extra files, unable to remove', torrent.directory)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ def sp(path, *args):
|
|||||||
# Replace *NIX ambiguous '//' at the beginning of a path with '/' (crashes guessit)
|
# Replace *NIX ambiguous '//' at the beginning of a path with '/' (crashes guessit)
|
||||||
path = re.sub('^//', '/', path)
|
path = re.sub('^//', '/', path)
|
||||||
|
|
||||||
return toUnicode(path)
|
return path
|
||||||
|
|
||||||
|
|
||||||
def ek(original, *args):
|
def ek(original, *args):
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class MediaBase(Plugin):
|
|||||||
|
|
||||||
def onComplete():
|
def onComplete():
|
||||||
try:
|
try:
|
||||||
db = get_db()
|
|
||||||
media = fireEvent('media.get', media_id, single = True)
|
media = fireEvent('media.get', media_id, single = True)
|
||||||
event_name = '%s.searcher.single' % media.get('type')
|
event_name = '%s.searcher.single' % media.get('type')
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ from couchpotato.core.helpers.encoding import simplifyString"""
|
|||||||
|
|
||||||
|
|
||||||
class TitleIndex(TreeBasedIndex):
|
class TitleIndex(TreeBasedIndex):
|
||||||
_version = 2
|
_version = 3
|
||||||
|
|
||||||
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
|
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
|
||||||
from string import ascii_letters
|
from string import ascii_letters
|
||||||
@@ -123,7 +123,7 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
|
|||||||
nr_prefix = '' if title and len(title) > 0 and title[0] in ascii_letters else '#'
|
nr_prefix = '' if title and len(title) > 0 and title[0] in ascii_letters else '#'
|
||||||
title = simplifyString(title)
|
title = simplifyString(title)
|
||||||
|
|
||||||
for prefix in ['the ']:
|
for prefix in ['the ', 'an ', 'a ']:
|
||||||
if prefix == title[:len(prefix)]:
|
if prefix == title[:len(prefix)]:
|
||||||
title = title[len(prefix):]
|
title = title[len(prefix):]
|
||||||
break
|
break
|
||||||
@@ -132,7 +132,7 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
|
|||||||
|
|
||||||
|
|
||||||
class StartsWithIndex(TreeBasedIndex):
|
class StartsWithIndex(TreeBasedIndex):
|
||||||
_version = 2
|
_version = 3
|
||||||
|
|
||||||
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
|
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
|
||||||
from string import ascii_letters
|
from string import ascii_letters
|
||||||
@@ -153,7 +153,7 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
|
|||||||
title = toUnicode(title)
|
title = toUnicode(title)
|
||||||
title = simplifyString(title)
|
title = simplifyString(title)
|
||||||
|
|
||||||
for prefix in ['the ']:
|
for prefix in ['the ', 'an ', 'a ']:
|
||||||
if prefix == title[:len(prefix)]:
|
if prefix == title[:len(prefix)]:
|
||||||
title = title[len(prefix):]
|
title = title[len(prefix):]
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -107,8 +107,7 @@ class MediaPlugin(MediaBase):
|
|||||||
def handler():
|
def handler():
|
||||||
fireEvent(event, media_id = media_id, on_complete = self.createOnComplete(media_id))
|
fireEvent(event, media_id = media_id, on_complete = self.createOnComplete(media_id))
|
||||||
|
|
||||||
if handler:
|
return handler
|
||||||
return handler
|
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Refresh handler for non existing media: %s', traceback.format_exc())
|
log.error('Refresh handler for non existing media: %s', traceback.format_exc())
|
||||||
@@ -361,13 +360,18 @@ class MediaPlugin(MediaBase):
|
|||||||
media = db.get('id', media_id)
|
media = db.get('id', media_id)
|
||||||
if media:
|
if media:
|
||||||
deleted = False
|
deleted = False
|
||||||
|
|
||||||
|
media_releases = fireEvent('release.for_media', media['_id'], single = True)
|
||||||
|
|
||||||
if delete_from == 'all':
|
if delete_from == 'all':
|
||||||
|
# Delete connected releases
|
||||||
|
for release in media_releases:
|
||||||
|
db.delete(release)
|
||||||
|
|
||||||
db.delete(media)
|
db.delete(media)
|
||||||
deleted = True
|
deleted = True
|
||||||
else:
|
else:
|
||||||
|
|
||||||
media_releases = fireEvent('release.for_media', media['_id'], single = True)
|
|
||||||
|
|
||||||
total_releases = len(media_releases)
|
total_releases = len(media_releases)
|
||||||
total_deleted = 0
|
total_deleted = 0
|
||||||
new_media_status = None
|
new_media_status = None
|
||||||
|
|||||||
@@ -88,10 +88,14 @@ class Provider(Plugin):
|
|||||||
|
|
||||||
if data and len(data) > 0:
|
if data and len(data) > 0:
|
||||||
try:
|
try:
|
||||||
data = XMLTree.fromstring(ss(data))
|
data = XMLTree.fromstring(data)
|
||||||
return self.getElements(data, item_path)
|
return self.getElements(data, item_path)
|
||||||
except:
|
except:
|
||||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
try:
|
||||||
|
data = XMLTree.fromstring(ss(data))
|
||||||
|
return self.getElements(data, item_path)
|
||||||
|
except:
|
||||||
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -200,7 +204,7 @@ class YarrProvider(Provider):
|
|||||||
self._search(media, quality, results)
|
self._search(media, quality, results)
|
||||||
# Search possible titles
|
# Search possible titles
|
||||||
else:
|
else:
|
||||||
media_title = fireEvent('library.query', media, single = True)
|
media_title = fireEvent('library.query', media, include_year = False, single = True)
|
||||||
|
|
||||||
for title in possibleTitles(media_title):
|
for title in possibleTitles(media_title):
|
||||||
self._searchOnTitle(title, media, quality, results)
|
self._searchOnTitle(title, media, quality, results)
|
||||||
@@ -298,7 +302,7 @@ class ResultList(list):
|
|||||||
old_score = new_result['score']
|
old_score = new_result['score']
|
||||||
new_result['score'] = int(old_score * is_correct_weight)
|
new_result['score'] = int(old_score * is_correct_weight)
|
||||||
|
|
||||||
log.info('Found correct release with weight %.02f, old_score(%d) now scaled to score(%d)', (
|
log.info2('Found correct release with weight %.02f, old_score(%d) now scaled to score(%d)', (
|
||||||
is_correct_weight,
|
is_correct_weight,
|
||||||
old_score,
|
old_score,
|
||||||
new_result['score']
|
new_result['score']
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import re
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt, simplifyString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.providers.nzb.base import NZBProvider
|
from couchpotato.core.media._base.providers.nzb.base import NZBProvider
|
||||||
|
|
||||||
@@ -50,8 +50,8 @@ class Base(NZBProvider):
|
|||||||
|
|
||||||
def extra_check(item):
|
def extra_check(item):
|
||||||
parts = re.search('available:.(?P<parts>\d+)./.(?P<total>\d+)', info.text)
|
parts = re.search('available:.(?P<parts>\d+)./.(?P<total>\d+)', info.text)
|
||||||
total = tryInt(parts.group('total'))
|
total = float(tryInt(parts.group('total')))
|
||||||
parts = tryInt(parts.group('parts'))
|
parts = float(tryInt(parts.group('parts')))
|
||||||
|
|
||||||
if (total / parts) < 1 and ((total / parts) < 0.95 or ((total / parts) >= 0.95 and not ('par2' in info.text.lower() or 'pa3' in info.text.lower()))):
|
if (total / parts) < 1 and ((total / parts) < 0.95 or ((total / parts) >= 0.95 and not ('par2' in info.text.lower() or 'pa3' in info.text.lower()))):
|
||||||
log.info2('Wrong: \'%s\', not complete: %s out of %s', (item['name'], parts, total))
|
log.info2('Wrong: \'%s\', not complete: %s out of %s', (item['name'], parts, total))
|
||||||
@@ -65,7 +65,7 @@ class Base(NZBProvider):
|
|||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': nzb_id,
|
'id': nzb_id,
|
||||||
'name': title.text,
|
'name': simplifyString(title.text),
|
||||||
'age': tryInt(age),
|
'age': tryInt(age),
|
||||||
'size': self.parseSize(size_match.group('size')),
|
'size': self.parseSize(size_match.group('size')),
|
||||||
'url': self.urls['download'] % nzb_id,
|
'url': self.urls['download'] % nzb_id,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from urllib2 import HTTPError
|
|||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import re
|
||||||
import urllib2
|
import urllib2
|
||||||
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
||||||
@@ -20,10 +21,11 @@ log = CPLog(__name__)
|
|||||||
class Base(NZBProvider, RSS):
|
class Base(NZBProvider, RSS):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'detail': 'details&id=%s',
|
'detail': 'details/%s',
|
||||||
'download': 't=get&id=%s'
|
'download': 't=get&id=%s'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
passwords_regex = 'password|wachtwoord'
|
||||||
limits_reached = {}
|
limits_reached = {}
|
||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
@@ -43,10 +45,8 @@ class Base(NZBProvider, RSS):
|
|||||||
|
|
||||||
def _searchOnHost(self, host, media, quality, results):
|
def _searchOnHost(self, host, media, quality, results):
|
||||||
|
|
||||||
query = self.buildUrl(media, host['api_key'])
|
query = self.buildUrl(media, host)
|
||||||
|
|
||||||
url = '%s&%s' % (self.getUrl(host['host']), query)
|
url = '%s&%s' % (self.getUrl(host['host']), query)
|
||||||
|
|
||||||
nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
|
|
||||||
for nzb in nzbs:
|
for nzb in nzbs:
|
||||||
@@ -79,6 +79,23 @@ class Base(NZBProvider, RSS):
|
|||||||
if spotter:
|
if spotter:
|
||||||
name_extra = spotter
|
name_extra = spotter
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
if "@spot.net" in nzb_id:
|
||||||
|
try:
|
||||||
|
# Get details for extended description to retrieve passwords
|
||||||
|
query = self.buildDetailsUrl(nzb_id, host['api_key'])
|
||||||
|
url = '%s&%s' % (self.getUrl(host['host']), query)
|
||||||
|
nzb_details = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})[0]
|
||||||
|
|
||||||
|
description = self.getTextElement(nzb_details, 'description')
|
||||||
|
|
||||||
|
# Extract a password from the description
|
||||||
|
password = re.search('(?:' + self.passwords_regex + ')(?: *)(?:\:|\=)(?: *)(.*?)\<br\>|\n|$', description, flags = re.I).group(1)
|
||||||
|
if password:
|
||||||
|
name = name + ' {{%s}}' % password.strip()
|
||||||
|
except:
|
||||||
|
log.debug('Error getting details of "%s": %s', (name, traceback.format_exc()))
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': nzb_id,
|
'id': nzb_id,
|
||||||
'provider_extra': urlparse(host['host']).hostname or host['host'],
|
'provider_extra': urlparse(host['host']).hostname or host['host'],
|
||||||
@@ -87,8 +104,9 @@ class Base(NZBProvider, RSS):
|
|||||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
||||||
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
|
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
|
||||||
'url': ((self.getUrl(host['host']) + self.urls['download']) % tryUrlencode(nzb_id)) + self.getApiExt(host),
|
'url': ((self.getUrl(host['host']) + self.urls['download']) % tryUrlencode(nzb_id)) + self.getApiExt(host),
|
||||||
'detail_url': '%sdetails/%s' % (cleanHost(host['host']), tryUrlencode(nzb_id)),
|
'detail_url': (cleanHost(host['host']) + self.urls['detail']) % tryUrlencode(nzb_id),
|
||||||
'content': self.getTextElement(nzb, 'description'),
|
'content': self.getTextElement(nzb, 'description'),
|
||||||
|
'description': description,
|
||||||
'score': host['extra_score'],
|
'score': host['extra_score'],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -191,6 +209,15 @@ class Base(NZBProvider, RSS):
|
|||||||
|
|
||||||
return 'try_next'
|
return 'try_next'
|
||||||
|
|
||||||
|
def buildDetailsUrl(self, nzb_id, api_key):
|
||||||
|
query = tryUrlencode({
|
||||||
|
't': 'details',
|
||||||
|
'id': nzb_id,
|
||||||
|
'apikey': api_key,
|
||||||
|
})
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
config = [{
|
config = [{
|
||||||
'name': 'newznab',
|
'name': 'newznab',
|
||||||
@@ -217,7 +244,7 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'host',
|
'name': 'host',
|
||||||
'default': 'api.nzb.su,dognzb.cr,nzbs.org,https://index.nzbgeek.info, https://smackdownonyou.com, https://www.nzbfinder.ws',
|
'default': 'api.nzb.su,api.dognzb.cr,nzbs.org,https://index.nzbgeek.info, https://smackdownonyou.com, https://www.nzbfinder.ws',
|
||||||
'description': 'The hostname of your newznab provider',
|
'description': 'The hostname of your newznab provider',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ class TorrentProvider(YarrProvider):
|
|||||||
|
|
||||||
prop_name = 'proxy.%s' % proxy
|
prop_name = 'proxy.%s' % proxy
|
||||||
last_check = float(Env.prop(prop_name, default = 0))
|
last_check = float(Env.prop(prop_name, default = 0))
|
||||||
if last_check > time.time() - 1209600:
|
|
||||||
|
if last_check > time.time() - 86400:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
data = ''
|
data = ''
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _search(self, media, quality, results):
|
||||||
|
|
||||||
query = self.buildUrl(media)
|
query = self.buildUrl(media, quality)
|
||||||
|
|
||||||
url = "%s&%s" % (self.urls['search'], query)
|
url = "%s&%s" % (self.urls['search'], query)
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class Base(TorrentProvider):
|
|||||||
'test': 'https://www.bitsoup.me/',
|
'test': 'https://www.bitsoup.me/',
|
||||||
'login': 'https://www.bitsoup.me/takelogin.php',
|
'login': 'https://www.bitsoup.me/takelogin.php',
|
||||||
'login_check': 'https://www.bitsoup.me/my.php',
|
'login_check': 'https://www.bitsoup.me/my.php',
|
||||||
'search': 'https://www.bitsoup.me/browse.php?',
|
'search': 'https://www.bitsoup.me/browse.php?%s',
|
||||||
'baseurl': 'https://www.bitsoup.me/%s',
|
'baseurl': 'https://www.bitsoup.me/%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,12 +65,13 @@ class Base(TorrentMagnetProvider):
|
|||||||
if column_name:
|
if column_name:
|
||||||
|
|
||||||
if column_name == 'name':
|
if column_name == 'name':
|
||||||
link = td.find('div', {'class': 'torrentname'}).find_all('a')[1]
|
link = td.find('div', {'class': 'torrentname'}).find_all('a')[2]
|
||||||
new['id'] = temp.get('id')[-8:]
|
new['id'] = temp.get('id')[-7:]
|
||||||
new['name'] = link.text
|
new['name'] = link.text
|
||||||
new['url'] = td.find('a', 'imagnet')['href']
|
new['url'] = td.find('a', 'imagnet')['href']
|
||||||
new['detail_url'] = self.urls['detail'] % (self.getDomain(), link['href'][1:])
|
new['detail_url'] = self.urls['detail'] % (self.getDomain(), link['href'][1:])
|
||||||
new['score'] = 20 if td.find('a', 'iverif') else 0
|
new['verified'] = True if td.find('a', 'iverify') else False
|
||||||
|
new['score'] = 100 if new['verified'] else 0
|
||||||
elif column_name is 'size':
|
elif column_name is 'size':
|
||||||
new['size'] = self.parseSize(td.text)
|
new['size'] = self.parseSize(td.text)
|
||||||
elif column_name is 'age':
|
elif column_name is 'age':
|
||||||
@@ -82,6 +83,10 @@ class Base(TorrentMagnetProvider):
|
|||||||
|
|
||||||
nr += 1
|
nr += 1
|
||||||
|
|
||||||
|
# Only store verified torrents
|
||||||
|
if self.conf('only_verified') and not new['verified']:
|
||||||
|
continue
|
||||||
|
|
||||||
results.append(new)
|
results.append(new)
|
||||||
except:
|
except:
|
||||||
log.error('Failed parsing KickAssTorrents: %s', traceback.format_exc())
|
log.error('Failed parsing KickAssTorrents: %s', traceback.format_exc())
|
||||||
@@ -151,6 +156,13 @@ config = [{
|
|||||||
'default': 40,
|
'default': 40,
|
||||||
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
|
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'only_verified',
|
||||||
|
'advanced': True,
|
||||||
|
'type': 'bool',
|
||||||
|
'default': False,
|
||||||
|
'description': 'Only search for verified releases.'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'extra_score',
|
'name': 'extra_score',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _searchOnTitle(self, title, media, quality, results):
|
||||||
|
|
||||||
url = self.buildUrl(media, quality)
|
url = self.buildUrl(title, media, quality)
|
||||||
data = self.getHTMLData(url)
|
data = self.getHTMLData(url)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Base(TorrentMagnetProvider):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
entries = results_table.find_all('tr')
|
entries = results_table.find_all('tr')
|
||||||
for result in entries[2:]:
|
for result in entries[1:]:
|
||||||
link = result.find(href = re.compile('torrent\/\d+\/'))
|
link = result.find(href = re.compile('torrent\/\d+\/'))
|
||||||
download = result.find(href = re.compile('magnet:'))
|
download = result.find(href = re.compile('magnet:'))
|
||||||
|
|
||||||
@@ -109,7 +109,11 @@ class Base(TorrentMagnetProvider):
|
|||||||
full_description = self.getCache('tpb.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
full_description = self.getCache('tpb.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
||||||
html = BeautifulSoup(full_description)
|
html = BeautifulSoup(full_description)
|
||||||
nfo_pre = html.find('div', attrs = {'class': 'nfo'})
|
nfo_pre = html.find('div', attrs = {'class': 'nfo'})
|
||||||
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
description = ''
|
||||||
|
try:
|
||||||
|
description = toUnicode(nfo_pre.text)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
item['description'] = description
|
item['description'] = description
|
||||||
return item
|
return item
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
||||||
@@ -18,9 +19,9 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _searchOnTitle(self, title, media, quality, results):
|
||||||
|
|
||||||
query = self.buildUrl(media)
|
query = '"%s" %s' % (title, media['info']['year'])
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'/browse.php?': None,
|
'/browse.php?': None,
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
def _search(self, movie, quality, results):
|
def _search(self, movie, quality, results):
|
||||||
|
|
||||||
search_url = self.urls['search'] % (self.getDomain(), getIdentifier(movie), quality['identifier'])
|
domain = self.getDomain()
|
||||||
|
if not domain:
|
||||||
|
return
|
||||||
|
|
||||||
|
search_url = self.urls['search'] % (domain, getIdentifier(movie), quality['identifier'])
|
||||||
|
|
||||||
data = self.getJsonData(search_url)
|
data = self.getJsonData(search_url)
|
||||||
|
|
||||||
@@ -43,21 +47,19 @@ class Base(TorrentProvider):
|
|||||||
try:
|
try:
|
||||||
for result in data.get('MovieList'):
|
for result in data.get('MovieList'):
|
||||||
|
|
||||||
try:
|
if result['Quality'] and result['Quality'] not in result['MovieTitle']:
|
||||||
title = result['TorrentUrl'].split('/')[-1][:-8].replace('_', '.').strip('._')
|
title = result['MovieTitle'] + ' BrRip ' + result['Quality']
|
||||||
title = title.replace('.-.', '-')
|
else:
|
||||||
title = title.replace('..', '.')
|
title = result['MovieTitle'] + ' BrRip'
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': result['MovieID'],
|
'id': result['MovieID'],
|
||||||
'name': title,
|
'name': title,
|
||||||
'url': result['TorrentMagnetUrl'],
|
'url': result['TorrentMagnetUrl'],
|
||||||
'detail_url': self.urls['detail'] % (self.getDomain(), result['MovieID']),
|
'detail_url': self.urls['detail'] % (domain, result['MovieID']),
|
||||||
'size': self.parseSize(result['Size']),
|
'size': self.parseSize(result['Size']),
|
||||||
'seeders': tryInt(result['TorrentSeeds']),
|
'seeders': tryInt(result['TorrentSeeds']),
|
||||||
'leechers': tryInt(result['TorrentPeers'])
|
'leechers': tryInt(result['TorrentPeers']),
|
||||||
})
|
})
|
||||||
|
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -87,31 +87,23 @@ class Searcher(SearcherBase):
|
|||||||
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = None):
|
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = None):
|
||||||
if not preferred_quality: preferred_quality = {}
|
if not preferred_quality: preferred_quality = {}
|
||||||
|
|
||||||
name = nzb['name']
|
|
||||||
size = nzb.get('size', 0)
|
|
||||||
nzb_words = re.split('\W+', simplifyString(name))
|
|
||||||
|
|
||||||
qualities = fireEvent('quality.all', single = True)
|
|
||||||
|
|
||||||
found = {}
|
found = {}
|
||||||
for quality in qualities:
|
|
||||||
# Main in words
|
|
||||||
if quality['identifier'] in nzb_words:
|
|
||||||
found[quality['identifier']] = True
|
|
||||||
|
|
||||||
# Alt in words
|
|
||||||
if list(set(nzb_words) & set(quality['alternative'])):
|
|
||||||
found[quality['identifier']] = True
|
|
||||||
|
|
||||||
# Try guessing via quality tags
|
# Try guessing via quality tags
|
||||||
guess = fireEvent('quality.guess', [nzb.get('name')], single = True)
|
guess = fireEvent('quality.guess', files = [nzb.get('name')], size = nzb.get('size', None), single = True)
|
||||||
if guess:
|
if guess:
|
||||||
found[guess['identifier']] = True
|
found[guess['identifier']] = True
|
||||||
|
|
||||||
# Hack for older movies that don't contain quality tag
|
# Hack for older movies that don't contain quality tag
|
||||||
|
name = nzb['name']
|
||||||
|
size = nzb.get('size', 0)
|
||||||
|
|
||||||
year_name = fireEvent('scanner.name_year', name, single = True)
|
year_name = fireEvent('scanner.name_year', name, single = True)
|
||||||
if len(found) == 0 and movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
|
if len(found) == 0 and movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
|
||||||
if size > 3000: # Assume dvdr
|
if size > 20000: # Assume bd50
|
||||||
|
log.info('Quality was missing in name, assuming it\'s a BR-Disk based on the size: %s', size)
|
||||||
|
found['bd50'] = True
|
||||||
|
elif size > 3000: # Assume dvdr
|
||||||
log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', size)
|
log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', size)
|
||||||
found['dvdr'] = True
|
found['dvdr'] = True
|
||||||
else: # Assume dvdrip
|
else: # Assume dvdrip
|
||||||
@@ -123,7 +115,10 @@ class Searcher(SearcherBase):
|
|||||||
if found.get(allowed):
|
if found.get(allowed):
|
||||||
del found[allowed]
|
del found[allowed]
|
||||||
|
|
||||||
return not (found.get(preferred_quality['identifier']) and len(found) == 1)
|
if found.get(preferred_quality['identifier']) and len(found) == 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return found
|
||||||
|
|
||||||
def correct3D(self, nzb, preferred_quality = None):
|
def correct3D(self, nzb, preferred_quality = None):
|
||||||
if not preferred_quality: preferred_quality = {}
|
if not preferred_quality: preferred_quality = {}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class MovieBase(MovieTypeBase):
|
|||||||
|
|
||||||
# Clean snatched history
|
# Clean snatched history
|
||||||
for release in fireEvent('release.for_media', m['_id'], single = True):
|
for release in fireEvent('release.for_media', m['_id'], single = True):
|
||||||
if release.get('status') in ['downloaded', 'snatched', 'done']:
|
if release.get('status') in ['downloaded', 'snatched', 'seeding', 'done']:
|
||||||
if params.get('ignore_previous', False):
|
if params.get('ignore_previous', False):
|
||||||
release['status'] = 'ignored'
|
release['status'] = 'ignored'
|
||||||
db.update(release)
|
db.update(release)
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ var MovieList = new Class({
|
|||||||
(e).preventDefault();
|
(e).preventDefault();
|
||||||
this.set('text', 'Deleting..');
|
this.set('text', 'Deleting..');
|
||||||
Api.request('media.delete', {
|
Api.request('media.delete', {
|
||||||
|
'method': 'post',
|
||||||
'data': {
|
'data': {
|
||||||
'id': ids.join(','),
|
'id': ids.join(','),
|
||||||
'delete_from': self.options.identifier
|
'delete_from': self.options.identifier
|
||||||
@@ -413,6 +414,7 @@ var MovieList = new Class({
|
|||||||
var ids = self.getSelectedMovies();
|
var ids = self.getSelectedMovies();
|
||||||
|
|
||||||
Api.request('movie.edit', {
|
Api.request('movie.edit', {
|
||||||
|
'method': 'post',
|
||||||
'data': {
|
'data': {
|
||||||
'id': ids.join(','),
|
'id': ids.join(','),
|
||||||
'profile_id': self.mass_edit_quality.get('value')
|
'profile_id': self.mass_edit_quality.get('value')
|
||||||
@@ -426,6 +428,7 @@ var MovieList = new Class({
|
|||||||
var ids = self.getSelectedMovies();
|
var ids = self.getSelectedMovies();
|
||||||
|
|
||||||
Api.request('media.refresh', {
|
Api.request('media.refresh', {
|
||||||
|
'method': 'post',
|
||||||
'data': {
|
'data': {
|
||||||
'id': ids.join(',')
|
'id': ids.join(',')
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -2,6 +2,7 @@ Page.Movies.Manage = new Class({
|
|||||||
|
|
||||||
Extends: PageBase,
|
Extends: PageBase,
|
||||||
|
|
||||||
|
order: 20,
|
||||||
name: 'manage',
|
name: 'manage',
|
||||||
title: 'Do stuff to your existing movies!',
|
title: 'Do stuff to your existing movies!',
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ MA.IMDB = new Class({
|
|||||||
create: function(){
|
create: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.id = self.movie.get('imdb') || self.movie.get('identifier');
|
self.id = self.movie.getIdentifier ? self.movie.getIdentifier() : self.get('imdb');
|
||||||
|
|
||||||
self.el = new Element('a.imdb', {
|
self.el = new Element('a.imdb', {
|
||||||
'title': 'Go to the IMDB page of ' + self.getTitle(),
|
'title': 'Go to the IMDB page of ' + self.getTitle(),
|
||||||
@@ -684,7 +684,7 @@ MA.Readd = new Class({
|
|||||||
var movie_done = self.movie.data.status == 'done';
|
var movie_done = self.movie.data.status == 'done';
|
||||||
if(self.movie.data.releases && !movie_done)
|
if(self.movie.data.releases && !movie_done)
|
||||||
var snatched = self.movie.data.releases.filter(function(release){
|
var snatched = self.movie.data.releases.filter(function(release){
|
||||||
return release.status && (release.status == 'snatched' || release.status == 'downloaded' || release.status == 'done');
|
return release.status && (release.status == 'snatched' || release.status == 'seeding' || release.status == 'downloaded' || release.status == 'done');
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
if(movie_done || snatched && snatched > 0)
|
if(movie_done || snatched && snatched > 0)
|
||||||
@@ -703,7 +703,7 @@ MA.Readd = new Class({
|
|||||||
|
|
||||||
Api.request('movie.add', {
|
Api.request('movie.add', {
|
||||||
'data': {
|
'data': {
|
||||||
'identifier': self.movie.get('identifier'),
|
'identifier': self.movie.getIdentifier(),
|
||||||
'ignore_previous': 1
|
'ignore_previous': 1
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
.movies.thumbs_list .movie {
|
.movies.thumbs_list .movie {
|
||||||
width: 16.66667%;
|
width: 16.66667%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
min-height: 200px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -133,6 +134,7 @@
|
|||||||
@media all and (max-width: 800px) {
|
@media all and (max-width: 800px) {
|
||||||
.movies.thumbs_list .movie {
|
.movies.thumbs_list .movie {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
|
min-height: 100px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -250,6 +250,10 @@ var Movie = new Class({
|
|||||||
getUnprefixedTitle: function(t){
|
getUnprefixedTitle: function(t){
|
||||||
if(t.substr(0, 4).toLowerCase() == 'the ')
|
if(t.substr(0, 4).toLowerCase() == 'the ')
|
||||||
t = t.substr(4) + ', The';
|
t = t.substr(4) + ', The';
|
||||||
|
else if(t.substr(0, 3).toLowerCase() == 'an ')
|
||||||
|
t = t.substr(3) + ', An';
|
||||||
|
else if(t.substr(0, 2).toLowerCase() == 'a ')
|
||||||
|
t = t.substr(2) + ', A';
|
||||||
return t;
|
return t;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -296,6 +300,17 @@ var Movie = new Class({
|
|||||||
self.el.removeClass(self.view+'_view')
|
self.el.removeClass(self.view+'_view')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getIdentifier: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return self.get('identifiers').imdb;
|
||||||
|
}
|
||||||
|
catch (e){ }
|
||||||
|
|
||||||
|
return self.get('imdb');
|
||||||
|
},
|
||||||
|
|
||||||
get: function(attr){
|
get: function(attr){
|
||||||
return this.data[attr] || this.data.info[attr]
|
return this.data[attr] || this.data.info[attr]
|
||||||
},
|
},
|
||||||
|
|||||||
+1
@@ -2,6 +2,7 @@ Page.Movies.Wanted = new Class({
|
|||||||
|
|
||||||
Extends: PageBase,
|
Extends: PageBase,
|
||||||
|
|
||||||
|
order: 10,
|
||||||
name: 'wanted',
|
name: 'wanted',
|
||||||
title: 'Gimmy gimmy gimmy!',
|
title: 'Gimmy gimmy gimmy!',
|
||||||
folder_browser: null,
|
folder_browser: null,
|
||||||
@@ -28,6 +28,20 @@ config = [{
|
|||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': '(hours)',
|
'description': '(hours)',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'hide_wanted',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Hide the chart movies that are already in your wanted list.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'hide_library',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Hide the chart movies that are already in your library.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class Charts(Plugin):
|
|||||||
'charts': charts
|
'charts': charts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def updateViewCache(self):
|
def updateViewCache(self):
|
||||||
|
|
||||||
if self.update_in_progress:
|
if self.update_in_progress:
|
||||||
@@ -49,6 +48,9 @@ class Charts(Plugin):
|
|||||||
try:
|
try:
|
||||||
self.update_in_progress = True
|
self.update_in_progress = True
|
||||||
charts = fireEvent('automation.get_chart_list', merge = True)
|
charts = fireEvent('automation.get_chart_list', merge = True)
|
||||||
|
for chart in charts:
|
||||||
|
chart['hide_wanted'] = self.conf('hide_wanted')
|
||||||
|
chart['hide_library'] = self.conf('hide_library')
|
||||||
self.setCache('charts_cached', charts, timeout = 7200 * tryInt(self.conf('update_interval', default = 12)))
|
self.setCache('charts_cached', charts, timeout = 7200 * tryInt(self.conf('update_interval', default = 12)))
|
||||||
except:
|
except:
|
||||||
log.error('Failed refreshing charts')
|
log.error('Failed refreshing charts')
|
||||||
|
|||||||
@@ -11,6 +11,17 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
max-height: 510px;
|
||||||
|
overflow: hidden;
|
||||||
|
scrollbar-base-color: #4e5969;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts .chart:hover {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts .chart .media_result.hidden {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.charts .refresh {
|
.charts .refresh {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ var Charts = new Class({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var in_database_class = movie.in_wanted ? 'chart_in_wanted' : (movie.in_library ? 'chart_in_library' : ''),
|
var in_database_class = (chart.hide_wanted && movie.in_wanted) ? 'hidden' : (movie.in_wanted ? 'chart_in_wanted' : ((chart.hide_library && movie.in_library) ? 'hidden': (movie.in_library ? 'chart_in_library' : ''))),
|
||||||
in_database_title = movie.in_wanted ? 'Movie in wanted list' : (movie.in_library ? 'Movie in library' : '');
|
in_database_title = movie.in_wanted ? 'Movie in wanted list' : (movie.in_library ? 'Movie in library' : '');
|
||||||
|
|
||||||
m.el
|
m.el
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato import fireEvent
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -82,6 +83,7 @@ class Bluray(Automation, RSS):
|
|||||||
def getChartList(self):
|
def getChartList(self):
|
||||||
# Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id)
|
# Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id)
|
||||||
movie_list = {'name': 'Blu-ray.com - New Releases', 'url': self.display_url, 'order': self.chart_order, 'list': []}
|
movie_list = {'name': 'Blu-ray.com - New Releases', 'url': self.display_url, 'order': self.chart_order, 'list': []}
|
||||||
|
movie_ids = []
|
||||||
max_items = int(self.conf('max_items', section='charts', default=5))
|
max_items = int(self.conf('max_items', section='charts', default=5))
|
||||||
rss_movies = self.getRSSData(self.rss_url)
|
rss_movies = self.getRSSData(self.rss_url)
|
||||||
|
|
||||||
@@ -95,6 +97,15 @@ class Bluray(Automation, RSS):
|
|||||||
movie = self.search(name, year)
|
movie = self.search(name, year)
|
||||||
|
|
||||||
if movie:
|
if movie:
|
||||||
|
|
||||||
|
if movie.get('imdb') in movie_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_movie = fireEvent('movie.is_movie', identifier = movie.get('imdb'), single = True)
|
||||||
|
if not is_movie:
|
||||||
|
continue
|
||||||
|
|
||||||
|
movie_ids.append(movie.get('imdb'))
|
||||||
movie_list['list'].append( movie )
|
movie_list['list'].append( movie )
|
||||||
if len(movie_list['list']) >= max_items:
|
if len(movie_list['list']) >= max_items:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -100,20 +100,28 @@ class IMDBAutomation(IMDBBase):
|
|||||||
|
|
||||||
enabled_option = 'automation_providers_enabled'
|
enabled_option = 'automation_providers_enabled'
|
||||||
|
|
||||||
chart_urls = {
|
charts = {
|
||||||
'theater': 'http://www.imdb.com/movies-in-theaters/',
|
'theater': {
|
||||||
'top250': 'http://www.imdb.com/chart/top',
|
'order': 1,
|
||||||
'boxoffice': 'http://www.imdb.com/chart/',
|
'name': 'IMDB - Movies in Theaters',
|
||||||
}
|
'url': 'http://www.imdb.com/movies-in-theaters/',
|
||||||
chart_names = {
|
},
|
||||||
'theater': 'IMDB - Movies in Theaters',
|
'boxoffice': {
|
||||||
'top250': 'IMDB - Top 250 Movies',
|
'order': 2,
|
||||||
'boxoffice': 'IMDB - Box Office',
|
'name': 'IMDB - Box Office',
|
||||||
}
|
'url': 'http://www.imdb.com/chart/',
|
||||||
chart_order = {
|
},
|
||||||
'theater': 2,
|
'rentals': {
|
||||||
'top250': 4,
|
'order': 3,
|
||||||
'boxoffice': 3,
|
'name': 'IMDB - Top DVD rentals',
|
||||||
|
'url': 'http://m.imdb.com/boxoffice_json',
|
||||||
|
'type': 'json',
|
||||||
|
},
|
||||||
|
'top250': {
|
||||||
|
'order': 4,
|
||||||
|
'name': 'IMDB - Top 250 Movies',
|
||||||
|
'url': 'http://www.imdb.com/chart/top',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
first_table = ['boxoffice']
|
first_table = ['boxoffice']
|
||||||
@@ -122,23 +130,30 @@ class IMDBAutomation(IMDBBase):
|
|||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
|
|
||||||
for url in self.chart_urls:
|
for name in self.charts:
|
||||||
if self.conf('automation_charts_%s' % url):
|
chart = self.charts[name]
|
||||||
data = self.getHTMLData(self.chart_urls[url])
|
url = chart.get('url')
|
||||||
|
|
||||||
|
if self.conf('automation_charts_%s' % name):
|
||||||
|
data = self.getHTMLData(url)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
html = BeautifulSoup(data)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result_div = html.find('div', attrs = {'id': 'main'})
|
html = BeautifulSoup(data)
|
||||||
|
|
||||||
try:
|
if chart.get('type', 'html') == 'html':
|
||||||
if url in self.first_table:
|
result_div = html.find('div', attrs = {'id': 'main'})
|
||||||
table = result_div.find('table')
|
|
||||||
result_div = table if table else result_div
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
imdb_ids = getImdb(str(result_div), multiple = True)
|
try:
|
||||||
|
if url in self.first_table:
|
||||||
|
table = result_div.find('table')
|
||||||
|
result_div = table if table else result_div
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
imdb_ids = getImdb(str(result_div), multiple = True)
|
||||||
|
else:
|
||||||
|
imdb_ids = getImdb(str(data), multiple = True)
|
||||||
|
|
||||||
for imdb_id in imdb_ids:
|
for imdb_id in imdb_ids:
|
||||||
info = self.getInfo(imdb_id)
|
info = self.getInfo(imdb_id)
|
||||||
@@ -153,42 +168,56 @@ class IMDBAutomation(IMDBBase):
|
|||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|
||||||
|
|
||||||
def getChartList(self):
|
def getChartList(self):
|
||||||
|
|
||||||
# Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id)
|
# Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id)
|
||||||
movie_lists = []
|
movie_lists = []
|
||||||
max_items = int(self.conf('max_items', section='charts', default=5))
|
max_items = int(self.conf('max_items', section = 'charts', default=5))
|
||||||
|
|
||||||
for url in self.chart_urls:
|
for name in self.charts:
|
||||||
if self.conf('chart_display_%s' % url):
|
chart = self.charts[name].copy()
|
||||||
movie_list = {'name': self.chart_names[url], 'url': self.chart_urls[url], 'order': self.chart_order[url], 'list': []}
|
url = chart.get('url')
|
||||||
data = self.getHTMLData(self.chart_urls[url])
|
|
||||||
|
if self.conf('chart_display_%s' % name):
|
||||||
|
|
||||||
|
chart['list'] = []
|
||||||
|
|
||||||
|
data = self.getHTMLData(url)
|
||||||
if data:
|
if data:
|
||||||
html = BeautifulSoup(data)
|
html = BeautifulSoup(data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result_div = html.find('div', attrs = {'id': 'main'})
|
|
||||||
|
|
||||||
try:
|
if chart.get('type', 'html') == 'html':
|
||||||
if url in self.first_table:
|
result_div = html.find('div', attrs = {'id': 'main'})
|
||||||
table = result_div.find('table')
|
|
||||||
result_div = table if table else result_div
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
imdb_ids = getImdb(str(result_div), multiple = True)
|
try:
|
||||||
|
if url in self.first_table:
|
||||||
|
table = result_div.find('table')
|
||||||
|
result_div = table if table else result_div
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
imdb_ids = getImdb(str(result_div), multiple = True)
|
||||||
|
else:
|
||||||
|
imdb_ids = getImdb(str(data), multiple = True)
|
||||||
|
|
||||||
for imdb_id in imdb_ids[0:max_items]:
|
for imdb_id in imdb_ids[0:max_items]:
|
||||||
|
|
||||||
|
is_movie = fireEvent('movie.is_movie', identifier = imdb_id, single = True)
|
||||||
|
if not is_movie:
|
||||||
|
continue
|
||||||
|
|
||||||
info = self.getInfo(imdb_id)
|
info = self.getInfo(imdb_id)
|
||||||
movie_list['list'].append(info)
|
chart['list'].append(info)
|
||||||
|
|
||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc()))
|
log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc()))
|
||||||
|
|
||||||
if movie_list['list']:
|
if chart['list']:
|
||||||
movie_lists.append(movie_list)
|
movie_lists.append(chart)
|
||||||
|
|
||||||
|
|
||||||
return movie_lists
|
return movie_lists
|
||||||
@@ -240,12 +269,19 @@ config = [{
|
|||||||
'description': 'New Movies <a href="http://www.imdb.com/movies-in-theaters/">In-Theaters</a> chart',
|
'description': 'New Movies <a href="http://www.imdb.com/movies-in-theaters/">In-Theaters</a> chart',
|
||||||
'default': True,
|
'default': True,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_charts_rentals',
|
||||||
|
'type': 'bool',
|
||||||
|
'label': 'DVD Rentals',
|
||||||
|
'description': 'Top DVD <a href="http://www.imdb.com/boxoffice/rentals/">rentals</a> chart',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'automation_charts_top250',
|
'name': 'automation_charts_top250',
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
'label': 'TOP 250',
|
'label': 'TOP 250',
|
||||||
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
|
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
|
||||||
'default': True,
|
'default': False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'automation_charts_boxoffice',
|
'name': 'automation_charts_boxoffice',
|
||||||
@@ -282,6 +318,13 @@ config = [{
|
|||||||
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
|
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
|
||||||
'default': False,
|
'default': False,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'chart_display_rentals',
|
||||||
|
'type': 'bool',
|
||||||
|
'label': 'DVD Rentals',
|
||||||
|
'description': 'Top DVD <a href="http://www.imdb.com/boxoffice/rentals/">rentals</a> chart',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'chart_display_boxoffice',
|
'name': 'chart_display_boxoffice',
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
|
|||||||
@@ -11,11 +11,16 @@ autoload = 'Newznab'
|
|||||||
|
|
||||||
class Newznab(MovieProvider, Base):
|
class Newznab(MovieProvider, Base):
|
||||||
|
|
||||||
def buildUrl(self, media, api_key):
|
def buildUrl(self, media, host):
|
||||||
|
|
||||||
query = tryUrlencode({
|
query = tryUrlencode({
|
||||||
't': 'movie',
|
't': 'movie',
|
||||||
'imdbid': getIdentifier(media).replace('tt', ''),
|
'imdbid': getIdentifier(media).replace('tt', ''),
|
||||||
'apikey': api_key,
|
'apikey': host['api_key'],
|
||||||
'extended': 1
|
'extended': 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if len(host.get('custom_tag', '')) > 0:
|
||||||
|
query = '%s&%s' % (query, host.get('custom_tag'))
|
||||||
|
|
||||||
return query
|
return query
|
||||||
@@ -10,10 +10,14 @@ autoload = 'BiTHDTV'
|
|||||||
|
|
||||||
|
|
||||||
class BiTHDTV(MovieProvider, Base):
|
class BiTHDTV(MovieProvider, Base):
|
||||||
|
cat_ids = [
|
||||||
|
([2], ['bd50']),
|
||||||
|
]
|
||||||
|
cat_backup_id = 7 # Movies
|
||||||
|
|
||||||
def buildUrl(self, media):
|
def buildUrl(self, media, quality):
|
||||||
query = tryUrlencode({
|
query = tryUrlencode({
|
||||||
'search': fireEvent('library.query', media, single = True),
|
'search': fireEvent('library.query', media, single = True),
|
||||||
'cat': 7 # Movie cat
|
'cat': self.getCatId(quality)[0]
|
||||||
})
|
})
|
||||||
return query
|
return query
|
||||||
@@ -18,6 +18,6 @@ class IPTorrents(MovieProvider, Base):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def buildUrl(self, title, media, quality):
|
def buildUrl(self, title, media, quality):
|
||||||
query = '%s %s' % (title.replace(':', ''), media['info']['year'])
|
query = '"%s" %s' % (title.replace(':', ''), media['info']['year'])
|
||||||
|
|
||||||
return self._buildUrl(query, quality)
|
return self._buildUrl(query, quality)
|
||||||
@@ -11,4 +11,4 @@ autoload = 'PublicHD'
|
|||||||
class PublicHD(MovieProvider, Base):
|
class PublicHD(MovieProvider, Base):
|
||||||
|
|
||||||
def buildUrl(self, media):
|
def buildUrl(self, media):
|
||||||
return fireEvent('library.query', media, single = True)
|
return fireEvent('library.query', media, single = True).replace(':', '')
|
||||||
@@ -17,13 +17,13 @@ class SceneAccess(MovieProvider, Base):
|
|||||||
([8], ['dvdr']),
|
([8], ['dvdr']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def buildUrl(self, media, quality):
|
def buildUrl(self, title, media, quality):
|
||||||
cat_id = self.getCatId(quality)[0]
|
cat_id = self.getCatId(quality)[0]
|
||||||
url = self.urls['search'] % (cat_id, cat_id)
|
url = self.urls['search'] % (cat_id, cat_id)
|
||||||
|
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'search': fireEvent('library.query', media, single = True),
|
'search': '%s %s' % (title, media['info']['year']),
|
||||||
'method': 3,
|
'method': 2,
|
||||||
})
|
})
|
||||||
query = "%s&%s" % (url, arguments)
|
query = "%s&%s" % (url, arguments)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class ThePirateBay(MovieProvider, Base):
|
|||||||
|
|
||||||
cat_ids = [
|
cat_ids = [
|
||||||
([209], ['3d']),
|
([209], ['3d']),
|
||||||
([207], ['720p', '1080p']),
|
([207], ['720p', '1080p', 'bd50']),
|
||||||
([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
||||||
([201, 207], ['brrip']),
|
([201, 207], ['brrip']),
|
||||||
([202], ['dvdr'])
|
([202], ['dvdr'])
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.media._base.providers.torrent.torrentday import Base
|
from couchpotato.core.media._base.providers.torrent.torrentday import Base
|
||||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||||
|
|
||||||
@@ -16,6 +15,3 @@ class TorrentDay(MovieProvider, Base):
|
|||||||
([3], ['dvdr']),
|
([3], ['dvdr']),
|
||||||
([5], ['bd50']),
|
([5], ['bd50']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def buildUrl(self, media):
|
|
||||||
return fireEvent('library.query', media, single = True)
|
|
||||||
@@ -31,6 +31,6 @@ class TorrentShack(MovieProvider, Base):
|
|||||||
|
|
||||||
def buildUrl(self, media, quality):
|
def buildUrl(self, media, quality):
|
||||||
query = (tryUrlencode(fireEvent('library.query', media, single = True)),
|
query = (tryUrlencode(fireEvent('library.query', media, single = True)),
|
||||||
self.getCatId(quality)[0],
|
self.getSceneOnly(),
|
||||||
self.getSceneOnly())
|
self.getCatId(quality)[0])
|
||||||
return query
|
return query
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato.core.media._base.providers.userscript.base import UserscriptBase
|
||||||
|
|
||||||
|
autoload = 'Filmstarts'
|
||||||
|
|
||||||
|
|
||||||
|
class Filmstarts(UserscriptBase):
|
||||||
|
|
||||||
|
includes = ['*://www.filmstarts.de/kritiken/*']
|
||||||
|
|
||||||
|
def getMovie(self, url):
|
||||||
|
try:
|
||||||
|
data = self.getUrl(url)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
html = BeautifulSoup(data)
|
||||||
|
table = html.find("table", attrs={"class": "table table-standard thead-standard table-striped_2 fs11"})
|
||||||
|
|
||||||
|
if table.find(text='Originaltitel'):
|
||||||
|
# Get original film title from the table specified above
|
||||||
|
name = table.find("div", text="Originaltitel").parent.parent.parent.td.text
|
||||||
|
else:
|
||||||
|
# If none is available get the title from the meta data
|
||||||
|
name = html.find("meta", {"property":"og:title"})['content']
|
||||||
|
|
||||||
|
# Year of production is not available in the meta data, so get it from the table
|
||||||
|
year = table.find("tr", text="Produktionsjahr").parent.parent.parent.td.text
|
||||||
|
|
||||||
|
return self.search(name, year)
|
||||||
@@ -126,6 +126,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
release_dates = fireEvent('movie.update_release_dates', movie['_id'], merge = True)
|
release_dates = fireEvent('movie.update_release_dates', movie['_id'], merge = True)
|
||||||
|
|
||||||
found_releases = []
|
found_releases = []
|
||||||
|
previous_releases = movie.get('releases', [])
|
||||||
too_early_to_search = []
|
too_early_to_search = []
|
||||||
|
|
||||||
default_title = getTitle(movie)
|
default_title = getTitle(movie)
|
||||||
@@ -139,16 +140,15 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
profile = db.get('id', movie['profile_id'])
|
profile = db.get('id', movie['profile_id'])
|
||||||
quality_order = fireEvent('quality.order', single = True)
|
|
||||||
|
|
||||||
ret = False
|
ret = False
|
||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
for q_identifier in profile.get('qualities'):
|
for q_identifier in profile.get('qualities'):
|
||||||
quality_custom = {
|
quality_custom = {
|
||||||
|
'index': index,
|
||||||
'quality': q_identifier,
|
'quality': q_identifier,
|
||||||
'finish': profile['finish'][index],
|
'finish': profile['finish'][index],
|
||||||
'wait_for': profile['wait_for'][index],
|
'wait_for': tryInt(profile['wait_for'][index]),
|
||||||
'3d': profile['3d'][index] if profile.get('3d') else False
|
'3d': profile['3d'][index] if profile.get('3d') else False
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,43 +162,51 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
|
|
||||||
# See if better quality is available
|
# See if better quality is available
|
||||||
for release in movie.get('releases', []):
|
for release in movie.get('releases', []):
|
||||||
if quality_order.index(release['quality']) <= quality_order.index(q_identifier) and release['status'] not in ['available', 'ignored', 'failed']:
|
if release['status'] not in ['available', 'ignored', 'failed']:
|
||||||
has_better_quality += 1
|
is_higher = fireEvent('quality.ishigher', \
|
||||||
|
{'identifier': q_identifier, 'is_3d': quality_custom.get('3d', 0)}, \
|
||||||
|
{'identifier': release['quality'], 'is_3d': release.get('is_3d', 0)}, \
|
||||||
|
profile, single = True)
|
||||||
|
if is_higher != 'higher':
|
||||||
|
has_better_quality += 1
|
||||||
|
|
||||||
# Don't search for quality lower then already available.
|
# Don't search for quality lower then already available.
|
||||||
if has_better_quality is 0:
|
if has_better_quality > 0:
|
||||||
|
|
||||||
quality = fireEvent('quality.single', identifier = q_identifier, single = True)
|
|
||||||
log.info('Search for %s in %s', (default_title, quality['label']))
|
|
||||||
|
|
||||||
# Extend quality with profile customs
|
|
||||||
quality['custom'] = quality_custom
|
|
||||||
|
|
||||||
results = fireEvent('searcher.search', search_protocols, movie, quality, single = True) or []
|
|
||||||
if len(results) == 0:
|
|
||||||
log.debug('Nothing found for %s in %s', (default_title, quality['label']))
|
|
||||||
|
|
||||||
# Check if movie isn't deleted while searching
|
|
||||||
if not fireEvent('media.get', movie.get('_id'), single = True):
|
|
||||||
break
|
|
||||||
|
|
||||||
# Add them to this movie releases list
|
|
||||||
found_releases += fireEvent('release.create_from_search', results, movie, quality, single = True)
|
|
||||||
|
|
||||||
# Try find a valid result and download it
|
|
||||||
if fireEvent('release.try_download_result', results, movie, quality_custom, manual, single = True):
|
|
||||||
ret = True
|
|
||||||
|
|
||||||
# Remove releases that aren't found anymore
|
|
||||||
for release in movie.get('releases', []):
|
|
||||||
if release.get('status') == 'available' and release.get('identifier') not in found_releases:
|
|
||||||
fireEvent('release.delete', release.get('_id'), single = True)
|
|
||||||
|
|
||||||
else:
|
|
||||||
log.info('Better quality (%s) already available or snatched for %s', (q_identifier, default_title))
|
log.info('Better quality (%s) already available or snatched for %s', (q_identifier, default_title))
|
||||||
fireEvent('media.restatus', movie['_id'])
|
fireEvent('media.restatus', movie['_id'])
|
||||||
break
|
break
|
||||||
|
|
||||||
|
quality = fireEvent('quality.single', identifier = q_identifier, single = True)
|
||||||
|
log.info('Search for %s in %s', (default_title, quality['label']))
|
||||||
|
|
||||||
|
# Extend quality with profile customs
|
||||||
|
quality['custom'] = quality_custom
|
||||||
|
|
||||||
|
results = fireEvent('searcher.search', search_protocols, movie, quality, single = True) or []
|
||||||
|
if len(results) == 0:
|
||||||
|
log.debug('Nothing found for %s in %s', (default_title, quality['label']))
|
||||||
|
|
||||||
|
# Check if movie isn't deleted while searching
|
||||||
|
if not fireEvent('media.get', movie.get('_id'), single = True):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Add them to this movie releases list
|
||||||
|
found_releases += fireEvent('release.create_from_search', results, movie, quality, single = True)
|
||||||
|
|
||||||
|
# Try find a valid result and download it
|
||||||
|
if fireEvent('release.try_download_result', results, movie, quality_custom, manual, single = True):
|
||||||
|
ret = True
|
||||||
|
|
||||||
|
# Remove releases that aren't found anymore
|
||||||
|
temp_previous_releases = []
|
||||||
|
for release in previous_releases:
|
||||||
|
if release.get('status') == 'available' and release.get('identifier') not in found_releases:
|
||||||
|
fireEvent('release.delete', release.get('_id'), single = True)
|
||||||
|
else:
|
||||||
|
temp_previous_releases.append(release)
|
||||||
|
previous_releases = temp_previous_releases
|
||||||
|
del temp_previous_releases
|
||||||
|
|
||||||
# Break if CP wants to shut down
|
# Break if CP wants to shut down
|
||||||
if self.shuttingDown() or ret:
|
if self.shuttingDown() or ret:
|
||||||
break
|
break
|
||||||
@@ -230,8 +238,9 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
preferred_quality = quality if quality else fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
preferred_quality = quality if quality else fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
||||||
|
|
||||||
# Contains lower quality string
|
# Contains lower quality string
|
||||||
if fireEvent('searcher.contains_other_quality', nzb, movie_year = media['info']['year'], preferred_quality = preferred_quality, single = True):
|
contains_other = fireEvent('searcher.contains_other_quality', nzb, movie_year = media['info']['year'], preferred_quality = preferred_quality, single = True)
|
||||||
log.info2('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
|
if contains_other != False:
|
||||||
|
log.info2('Wrong: %s, looking for %s, found %s', (nzb['name'], quality['label'], [x for x in contains_other] if contains_other else 'no quality'))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Contains lower quality string
|
# Contains lower quality string
|
||||||
@@ -288,7 +297,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
now_year = date.today().year
|
now_year = date.today().year
|
||||||
now_month = date.today().month
|
now_month = date.today().month
|
||||||
|
|
||||||
if (year is None or year < now_year - 1) and (not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0)):
|
if (year is None or year < now_year - 1 or (year <= now_year - 1 and now_month > 4)) and (not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0)):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class Plugin(object):
|
|||||||
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f)
|
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f)
|
||||||
|
|
||||||
def createFile(self, path, content, binary = False):
|
def createFile(self, path, content, binary = False):
|
||||||
path = ss(path)
|
path = sp(path)
|
||||||
|
|
||||||
self.makeDir(os.path.dirname(path))
|
self.makeDir(os.path.dirname(path))
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ class Plugin(object):
|
|||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
def makeDir(self, path):
|
def makeDir(self, path):
|
||||||
path = ss(path)
|
path = sp(path)
|
||||||
try:
|
try:
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
os.makedirs(path, Env.getPermission('folder'))
|
os.makedirs(path, Env.getPermission('folder'))
|
||||||
|
|||||||
@@ -79,7 +79,20 @@ class Dashboard(Plugin):
|
|||||||
# Don't list older movies
|
# 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
|
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)))):
|
(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:
|
if len(medias) >= limit:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
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.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -22,7 +23,11 @@ class Logging(Plugin):
|
|||||||
},
|
},
|
||||||
'return': {'type': 'object', 'example': """{
|
'return': {'type': 'object', 'example': """{
|
||||||
'success': True,
|
'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
|
'total': int, //Total log files available
|
||||||
}"""}
|
}"""}
|
||||||
})
|
})
|
||||||
@@ -34,7 +39,11 @@ class Logging(Plugin):
|
|||||||
},
|
},
|
||||||
'return': {'type': 'object', 'example': """{
|
'return': {'type': 'object', 'example': """{
|
||||||
'success': True,
|
'success': True,
|
||||||
'log': string, //Log file
|
'log': [{
|
||||||
|
'time': '03-12 09:12:59',
|
||||||
|
'type': 'INFO',
|
||||||
|
'message': 'Log message'
|
||||||
|
}, ..]
|
||||||
}"""}
|
}"""}
|
||||||
})
|
})
|
||||||
addApiView('logging.clear', self.clear, docs = {
|
addApiView('logging.clear', self.clear, docs = {
|
||||||
@@ -71,16 +80,18 @@ class Logging(Plugin):
|
|||||||
if current_path:
|
if current_path:
|
||||||
f = open(current_path, 'r')
|
f = open(current_path, 'r')
|
||||||
log_content = f.read()
|
log_content = f.read()
|
||||||
|
logs = self.toList(log_content)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'log': toUnicode(log_content),
|
'log': logs,
|
||||||
'total': total,
|
'total': total,
|
||||||
}
|
}
|
||||||
|
|
||||||
def partial(self, type = 'all', lines = 30, **kwargs):
|
def partial(self, type = 'all', lines = 30, offset = 0, **kwargs):
|
||||||
|
|
||||||
total_lines = tryInt(lines)
|
total_lines = tryInt(lines)
|
||||||
|
offset = tryInt(offset)
|
||||||
|
|
||||||
log_lines = []
|
log_lines = []
|
||||||
|
|
||||||
@@ -93,28 +104,57 @@ class Logging(Plugin):
|
|||||||
break
|
break
|
||||||
|
|
||||||
f = open(path, 'r')
|
f = open(path, 'r')
|
||||||
reversed_lines = toUnicode(f.read()).split('[0m\n')
|
log_content = toUnicode(f.read())
|
||||||
reversed_lines.reverse()
|
raw_lines = self.toList(log_content)
|
||||||
|
raw_lines.reverse()
|
||||||
|
|
||||||
brk = False
|
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)
|
log_lines.append(line)
|
||||||
|
|
||||||
if len(log_lines) >= total_lines:
|
if len(log_lines) >= (total_lines + offset):
|
||||||
brk = True
|
brk = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if brk:
|
if brk:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
log_lines = log_lines[offset:]
|
||||||
log_lines.reverse()
|
log_lines.reverse()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'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):
|
def clear(self, **kwargs):
|
||||||
|
|
||||||
for x in range(0, 50):
|
for x in range(0, 50):
|
||||||
|
|||||||
@@ -16,10 +16,14 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.log .nav li.select,
|
||||||
|
.page.log .nav li.clear {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.log .nav li:hover:not(.active) {
|
.page.log .nav li:hover:not(.active, .filter) {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
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 {
|
.page.log .loading {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@@ -50,28 +67,133 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif;
|
color: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.log .container .error {
|
.page.log .container select {
|
||||||
color: #FFA4A4;
|
vertical-align: top;
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
}
|
||||||
.page.log .container .debug { color: lightgrey; }
|
|
||||||
|
|
||||||
.page.log .container .time {
|
.page.log .container .time {
|
||||||
clear: both;
|
clear: both;
|
||||||
color: lightgrey;
|
color: lightgrey;
|
||||||
padding: 3px 0;
|
|
||||||
font-size: 10px;
|
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;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 3px;
|
||||||
|
font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif;
|
||||||
}
|
}
|
||||||
|
.page.log .container .time.highlight {
|
||||||
.page.log .container .time:last-child { display: none; }
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
.page.log .container .time span {
|
.page.log .container .time span {
|
||||||
float: right;
|
padding: 5px 0 3px;
|
||||||
width: 86%;
|
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,
|
Extends: PageBase,
|
||||||
|
|
||||||
|
order: 60,
|
||||||
name: 'log',
|
name: 'log',
|
||||||
title: 'Show recent logs.',
|
title: 'Show recent logs.',
|
||||||
has_tab: false,
|
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;
|
var self = this;
|
||||||
|
|
||||||
self.getLogs(0);
|
self.getLogs(0);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getLogs: function(nr){
|
getLogs: function (nr) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(self.log) self.log.destroy();
|
if (self.log) self.log.destroy();
|
||||||
self.log = new Element('div.container.loading', {
|
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);
|
}).inject(self.el);
|
||||||
|
|
||||||
Api.request('logging.get', {
|
Api.request('logging.get', {
|
||||||
'data': {
|
'data': {
|
||||||
'nr': nr
|
'nr': nr
|
||||||
},
|
},
|
||||||
'onComplete': function(json){
|
'onComplete': function (json) {
|
||||||
self.log.set('html', self.addColors(json.log));
|
self.log.set('text', '');
|
||||||
|
self.log_items = self.createLogElements(json.log);
|
||||||
|
self.log.adopt(self.log_items);
|
||||||
self.log.removeClass('loading');
|
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');
|
// Type selection
|
||||||
for (var i = 0; i <= json.total; i++) {
|
new Element('li.filter').grab(
|
||||||
new Element('li', {
|
new Element('select', {
|
||||||
'text': i+1,
|
|
||||||
'class': nr == i ? 'active': '',
|
|
||||||
'events': {
|
'events': {
|
||||||
'click': function(e){
|
'change': function () {
|
||||||
self.getLogs(e.target.get('text')-1);
|
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);
|
}).inject(nav);
|
||||||
}
|
}
|
||||||
|
|
||||||
new Element('li', {
|
// Clear button
|
||||||
|
new Element('li.clear', {
|
||||||
'text': 'clear',
|
'text': 'clear',
|
||||||
'events': {
|
'events': {
|
||||||
'click': function(){
|
'click': function () {
|
||||||
Api.request('logging.clear', {
|
Api.request('logging.clear', {
|
||||||
'onComplete': function(){
|
'onComplete': function () {
|
||||||
self.getLogs(0);
|
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
|
var elements = [];
|
||||||
.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>');
|
|
||||||
|
|
||||||
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():
|
if self.conf('cleanup') and full and not self.shuttingDown():
|
||||||
|
|
||||||
# Get movies with done status
|
# 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:
|
for done_movie in done_movies:
|
||||||
if getIdentifier(done_movie) not in added_identifiers:
|
if getIdentifier(done_movie) not in added_identifiers:
|
||||||
@@ -131,12 +131,16 @@ class Manage(Plugin):
|
|||||||
|
|
||||||
for release in releases:
|
for release in releases:
|
||||||
if release.get('files'):
|
if release.get('files'):
|
||||||
|
brk = False
|
||||||
for file_type in release.get('files', {}):
|
for file_type in release.get('files', {}):
|
||||||
for release_file in release['files'][file_type]:
|
for release_file in release['files'][file_type]:
|
||||||
# Remove release not available anymore
|
# Remove release not available anymore
|
||||||
if not os.path.isfile(sp(release_file)):
|
if not os.path.isfile(sp(release_file)):
|
||||||
fireEvent('release.clean', release['_id'])
|
fireEvent('release.clean', release['_id'])
|
||||||
|
brk = True
|
||||||
break
|
break
|
||||||
|
if brk:
|
||||||
|
break
|
||||||
|
|
||||||
# Check if there are duplicate releases (different quality) use the last one, delete the rest
|
# Check if there are duplicate releases (different quality) use the last one, delete the rest
|
||||||
if len(releases) > 1:
|
if len(releases) > 1:
|
||||||
@@ -147,10 +151,12 @@ class Manage(Plugin):
|
|||||||
already_used = used_files.get(release_file)
|
already_used = used_files.get(release_file)
|
||||||
|
|
||||||
if already_used:
|
if already_used:
|
||||||
|
# delete current one
|
||||||
if already_used.get('last_edit', 0) < release.get('last_edit', 0):
|
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:
|
else:
|
||||||
fireEvent('release.delete', already_used['_id'], single = True) # delete previous one
|
fireEvent('release.delete', already_used['_id'], single = True)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
used_files[release_file] = release
|
used_files[release_file] = release
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class ProfilePlugin(Plugin):
|
|||||||
})
|
})
|
||||||
|
|
||||||
addEvent('app.initialize', self.fill, priority = 90)
|
addEvent('app.initialize', self.fill, priority = 90)
|
||||||
addEvent('app.load', self.forceDefaults)
|
addEvent('app.load', self.forceDefaults, priority = 110)
|
||||||
|
|
||||||
def forceDefaults(self):
|
def forceDefaults(self):
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ class ProfilePlugin(Plugin):
|
|||||||
order = 0
|
order = 0
|
||||||
for type in kwargs.get('types', []):
|
for type in kwargs.get('types', []):
|
||||||
profile['qualities'].append(type.get('quality'))
|
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['finish'].append((tryInt(type.get('finish')) == 1) if order > 0 else True)
|
||||||
profile['3d'].append(tryInt(type.get('3d')))
|
profile['3d'].append(tryInt(type.get('3d')))
|
||||||
order += 1
|
order += 1
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ var Profile = new Class({
|
|||||||
new Element('span', {'text':'Wait'}),
|
new Element('span', {'text':'Wait'}),
|
||||||
new Element('input.inlay.xsmall', {
|
new Element('input.inlay.xsmall', {
|
||||||
'type':'text',
|
'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.'})
|
new Element('span', {'text':'day(s) for a better quality.'})
|
||||||
),
|
),
|
||||||
@@ -63,8 +63,7 @@ var Profile = new Class({
|
|||||||
data.types.include({
|
data.types.include({
|
||||||
'quality': quality,
|
'quality': quality,
|
||||||
'finish': data.finish[nr] || false,
|
'finish': data.finish[nr] || false,
|
||||||
'3d': data['3d'] ? data['3d'][nr] || false : false,
|
'3d': data['3d'] ? data['3d'][nr] || false : false
|
||||||
'wait_for': data.wait_for[nr] || 0
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -126,8 +125,7 @@ var Profile = new Class({
|
|||||||
data.types.include({
|
data.types.include({
|
||||||
'quality': type.getElement('select').get('value'),
|
'quality': type.getElement('select').get('value'),
|
||||||
'finish': +type.getElement('input.finish[type=checkbox]').checked,
|
'finish': +type.getElement('input.finish[type=checkbox]').checked,
|
||||||
'3d': +type.getElement('input.3d[type=checkbox]').checked,
|
'3d': +type.getElement('input.3d[type=checkbox]').checked
|
||||||
'wait_for': 0
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -340,8 +338,7 @@ Profile.Type = new Class({
|
|||||||
return {
|
return {
|
||||||
'quality': self.qualities.get('value'),
|
'quality': self.qualities.get('value'),
|
||||||
'finish': +self.finish.checked,
|
'finish': +self.finish.checked,
|
||||||
'3d': +self['3d'].checked,
|
'3d': +self['3d'].checked
|
||||||
'wait_for': 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ class QualityPlugin(Plugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
qualities = [
|
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': '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': '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': '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')]},
|
{'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': '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': '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':[]},
|
{'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']
|
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
||||||
threed_tags = {
|
threed_tags = {
|
||||||
'hsbs': [('half', 'sbs')],
|
'sbs': [('half', 'sbs'), 'hsbs', ('full', 'sbs'), 'fsbs'],
|
||||||
'fsbs': [('full', 'sbs')],
|
'ou': [('half', 'ou'), 'hou', ('full', 'ou'), 'fou'],
|
||||||
'3d': [],
|
'3d': ['2d3d', '3d2d'],
|
||||||
}
|
}
|
||||||
|
|
||||||
cached_qualities = None
|
cached_qualities = None
|
||||||
@@ -49,6 +49,8 @@ class QualityPlugin(Plugin):
|
|||||||
addEvent('quality.guess', self.guess)
|
addEvent('quality.guess', self.guess)
|
||||||
addEvent('quality.pre_releases', self.preReleases)
|
addEvent('quality.pre_releases', self.preReleases)
|
||||||
addEvent('quality.order', self.getOrder)
|
addEvent('quality.order', self.getOrder)
|
||||||
|
addEvent('quality.ishigher', self.isHigher)
|
||||||
|
addEvent('quality.isfinish', self.isFinish)
|
||||||
|
|
||||||
addApiView('quality.size.save', self.saveSize)
|
addApiView('quality.size.save', self.saveSize)
|
||||||
addApiView('quality.list', self.allView, docs = {
|
addApiView('quality.list', self.allView, docs = {
|
||||||
@@ -177,7 +179,7 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def guess(self, files, extra = None):
|
def guess(self, files, extra = None, size = None):
|
||||||
if not extra: extra = {}
|
if not extra: extra = {}
|
||||||
|
|
||||||
# Create hash for cache
|
# Create hash for cache
|
||||||
@@ -205,15 +207,20 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
self.calcScore(score, quality, contains_score, threedscore)
|
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
|
# Try again with loose testing
|
||||||
for quality in qualities:
|
for quality in qualities:
|
||||||
loose_score = self.guessLooseScore(quality, extra = extra)
|
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
|
has_non_zero = 0
|
||||||
for s in score:
|
for s in score:
|
||||||
if score[s] > 0:
|
if score[s]['score'] > 0:
|
||||||
has_non_zero += 1
|
has_non_zero += 1
|
||||||
|
|
||||||
if not has_non_zero:
|
if not has_non_zero:
|
||||||
@@ -281,7 +288,7 @@ class QualityPlugin(Plugin):
|
|||||||
return 1, key
|
return 1, key
|
||||||
|
|
||||||
if list(set([key]) & set(words)):
|
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 1, key
|
||||||
|
|
||||||
return 0, None
|
return 0, None
|
||||||
@@ -308,7 +315,20 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
return score
|
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
|
score[quality['identifier']]['score'] += add_score
|
||||||
|
|
||||||
@@ -325,30 +345,79 @@ class QualityPlugin(Plugin):
|
|||||||
for q in self.qualities:
|
for q in self.qualities:
|
||||||
self.cached_order[q.get('identifier')] = self.qualities.index(q)
|
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', []):
|
for allow in quality.get('allow', []):
|
||||||
score[allow]['score'] -= 40 if self.cached_order[allow] < self.cached_order[quality['identifier']] else 5
|
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):
|
def doTest(self):
|
||||||
|
|
||||||
tests = {
|
tests = {
|
||||||
'Movie Name (1999)-DVD-Rip.avi': 'dvdrip',
|
'Movie Name (1999)-DVD-Rip.avi': {'size': 700, 'quality': 'dvdrip'},
|
||||||
'Movie Name 1999 720p Bluray.mkv': '720p',
|
'Movie Name 1999 720p Bluray.mkv': {'size': 4200, 'quality': '720p'},
|
||||||
'Movie Name 1999 BR-Rip 720p.avi': 'brrip',
|
'Movie Name 1999 BR-Rip 720p.avi': {'size': 1000, 'quality': 'brrip'},
|
||||||
'Movie Name 1999 720p Web Rip.avi': 'scr',
|
'Movie Name 1999 720p Web Rip.avi': {'size': 1200, 'quality': 'scr'},
|
||||||
'Movie Name 1999 Web DL.avi': 'brrip',
|
'Movie Name 1999 Web DL.avi': {'size': 800, 'quality': 'brrip'},
|
||||||
'Movie.Name.1999.1080p.WEBRip.H264-Group': 'scr',
|
'Movie.Name.1999.1080p.WEBRip.H264-Group': {'size': 1500, 'quality': 'scr'},
|
||||||
'Movie.Name.1999.DVDRip-Group': 'dvdrip',
|
'Movie.Name.1999.DVDRip-Group': {'size': 750, 'quality': 'dvdrip'},
|
||||||
'Movie.Name.1999.DVD-Rip-Group': 'dvdrip',
|
'Movie.Name.1999.DVD-Rip-Group': {'size': 700, 'quality': 'dvdrip'},
|
||||||
'Movie.Name.1999.DVD-R-Group': 'dvdr',
|
'Movie.Name.1999.DVD-R-Group': {'size': 4500, 'quality': 'dvdr'},
|
||||||
'Movie.Name.Camelie.1999.720p.BluRay.x264-Group': '720p',
|
'Movie.Name.Camelie.1999.720p.BluRay.x264-Group': {'size': 5500, 'quality': '720p'},
|
||||||
'Movie.Name.2008.German.DL.AC3.1080p.BluRay.x264-Group': '1080p',
|
'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': '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
|
correct = 0
|
||||||
for name in tests:
|
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:
|
if not success:
|
||||||
log.error('%s failed check, thinks it\'s %s', (name, self.guess([name]).get('identifier')))
|
log.error('%s failed check, thinks it\'s %s', (name, self.guess([name]).get('identifier')))
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import os
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from CodernityDB.database import RecordDeleted
|
||||||
from couchpotato import md5, get_db
|
from couchpotato import md5, get_db
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
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.helpers.variable import getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
@@ -58,7 +59,7 @@ class Release(Plugin):
|
|||||||
|
|
||||||
# Clean releases that didn't have activity in the last week
|
# Clean releases that didn't have activity in the last week
|
||||||
addEvent('app.load', self.cleanDone)
|
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):
|
def cleanDone(self):
|
||||||
log.debug('Removing releases from dashboard')
|
log.debug('Removing releases from dashboard')
|
||||||
@@ -68,6 +69,27 @@ class Release(Plugin):
|
|||||||
|
|
||||||
db = get_db()
|
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
|
# get movies last_edit more than a week ago
|
||||||
medias = fireEvent('media.with_status', 'done', single = True)
|
medias = fireEvent('media.with_status', 'done', single = True)
|
||||||
|
|
||||||
@@ -107,6 +129,7 @@ class Release(Plugin):
|
|||||||
'media_id': media['_id'],
|
'media_id': media['_id'],
|
||||||
'identifier': release_identifier,
|
'identifier': release_identifier,
|
||||||
'quality': group['meta_data']['quality'].get('identifier'),
|
'quality': group['meta_data']['quality'].get('identifier'),
|
||||||
|
'is_3d': group['meta_data']['quality'].get('is_3d', 0),
|
||||||
'last_edit': int(time.time()),
|
'last_edit': int(time.time()),
|
||||||
'status': 'done'
|
'status': 'done'
|
||||||
}
|
}
|
||||||
@@ -157,15 +180,20 @@ class Release(Plugin):
|
|||||||
try:
|
try:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
rel = db.get('id', release_id)
|
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'])
|
self.delete(rel['_id'])
|
||||||
else:
|
else:
|
||||||
|
|
||||||
files = []
|
files = {}
|
||||||
for release_file in rel.get('files'):
|
for file_type in raw_files:
|
||||||
if os.path.isfile(ss(release_file['path'])):
|
|
||||||
files.append(release_file)
|
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
|
rel['files'] = files
|
||||||
db.update(rel)
|
db.update(rel)
|
||||||
@@ -315,10 +343,12 @@ class Release(Plugin):
|
|||||||
|
|
||||||
def tryDownloadResult(self, results, media, quality_custom, manual = False):
|
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:
|
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']:
|
if rel['status'] in ['ignored', 'failed']:
|
||||||
log.info('Ignored: %s', rel['name'])
|
log.info('Ignored: %s', rel['name'])
|
||||||
@@ -328,13 +358,30 @@ class Release(Plugin):
|
|||||||
log.info('Ignored, score to low: %s', rel['name'])
|
log.info('Ignored, score to low: %s', rel['name'])
|
||||||
continue
|
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)
|
downloaded = fireEvent('release.download', data = rel, media = media, manual = manual, single = True)
|
||||||
if downloaded is True:
|
if downloaded is True:
|
||||||
return True
|
return True
|
||||||
elif downloaded != 'try_next':
|
elif downloaded != 'try_next':
|
||||||
break
|
break
|
||||||
|
|
||||||
return False
|
return wait_for
|
||||||
|
|
||||||
def createFromSearch(self, search_results, media, quality):
|
def createFromSearch(self, search_results, media, quality):
|
||||||
|
|
||||||
@@ -406,7 +453,7 @@ class Release(Plugin):
|
|||||||
rel = db.get('id', release_id)
|
rel = db.get('id', release_id)
|
||||||
if rel and rel.get('status') != status:
|
if rel and rel.get('status') != status:
|
||||||
|
|
||||||
release_name = rel.get('name')
|
release_name = rel['info'].get('name')
|
||||||
if rel.get('files'):
|
if rel.get('files'):
|
||||||
for file_type in rel.get('files', {}):
|
for file_type in rel.get('files', {}):
|
||||||
if file_type == 'movie':
|
if file_type == 'movie':
|
||||||
|
|||||||
@@ -112,16 +112,13 @@ class Renamer(Plugin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not base_folder:
|
if not base_folder:
|
||||||
base_folder = self.conf('from')
|
base_folder = sp(self.conf('from'))
|
||||||
|
|
||||||
from_folder = sp(self.conf('from'))
|
from_folder = sp(self.conf('from'))
|
||||||
to_folder = sp(self.conf('to'))
|
to_folder = sp(self.conf('to'))
|
||||||
|
|
||||||
# Get media folder to process
|
# Get media folder to process
|
||||||
media_folder = release_download.get('folder')
|
media_folder = sp(release_download.get('folder'))
|
||||||
|
|
||||||
# Quality order for calculation quality priority
|
|
||||||
quality_order = fireEvent('quality.order', single = True)
|
|
||||||
|
|
||||||
# Get all folders that should not be processed
|
# Get all folders that should not be processed
|
||||||
no_process = [to_folder]
|
no_process = [to_folder]
|
||||||
@@ -149,9 +146,9 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
# Update to the from folder
|
# Update to the from folder
|
||||||
if len(release_download.get('files', [])) == 1:
|
if len(release_download.get('files', [])) == 1:
|
||||||
new_media_folder = from_folder
|
new_media_folder = sp(from_folder)
|
||||||
else:
|
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):
|
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)
|
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
|
# Put 'The' at the end
|
||||||
name_the = movie_name
|
name_the = movie_name
|
||||||
if movie_name[:4].lower() == 'the ':
|
for prefix in ['the ', 'an ', 'a ']:
|
||||||
name_the = movie_name[4:] + ', The'
|
if prefix == movie_name[:len(prefix)].lower():
|
||||||
|
name_the = movie_name[len(prefix):] + ', ' + prefix.strip().capitalize()
|
||||||
|
break
|
||||||
|
|
||||||
replacements = {
|
replacements = {
|
||||||
'ext': 'mkv',
|
'ext': 'mkv',
|
||||||
@@ -312,9 +311,15 @@ class Renamer(Plugin):
|
|||||||
'cd': '',
|
'cd': '',
|
||||||
'cd_nr': '',
|
'cd_nr': '',
|
||||||
'mpaa': media['info'].get('mpaa', ''),
|
'mpaa': media['info'].get('mpaa', ''),
|
||||||
|
'mpaa_only': media['info'].get('mpaa', ''),
|
||||||
'category': category_label,
|
'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']:
|
for file_type in group['files']:
|
||||||
|
|
||||||
# Move nfo depending on settings
|
# Move nfo depending on settings
|
||||||
@@ -410,8 +415,12 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
# Don't add language if multiple languages in 1 subtitle file
|
# Don't add language if multiple languages in 1 subtitle file
|
||||||
if len(sub_langs) == 1:
|
if len(sub_langs) == 1:
|
||||||
sub_name = sub_name.replace(replacements['ext'], '%s.%s' % (sub_langs[0], replacements['ext']))
|
sub_suffix = '%s.%s' % (sub_langs[0], replacements['ext'])
|
||||||
rename_files[current_file] = os.path.join(destination, final_folder_name, sub_name)
|
|
||||||
|
# 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)
|
rename_files = mergeDicts(rename_files, rename_extras)
|
||||||
|
|
||||||
@@ -438,17 +447,15 @@ class Renamer(Plugin):
|
|||||||
remove_leftovers = True
|
remove_leftovers = True
|
||||||
|
|
||||||
# Mark movie "done" once it's found the quality with the finish check
|
# Mark movie "done" once it's found the quality with the finish check
|
||||||
|
profile = None
|
||||||
try:
|
try:
|
||||||
if media.get('status') == 'active' and media.get('profile_id'):
|
if media.get('status') == 'active' and media.get('profile_id'):
|
||||||
profile = db.get('id', media['profile_id'])
|
profile = db.get('id', media['profile_id'])
|
||||||
if group['meta_data']['quality']['identifier'] in profile.get('qualities', []):
|
if fireEvent('quality.isfinish', group['meta_data']['quality'], profile, single = True):
|
||||||
nr = profile['qualities'].index(group['meta_data']['quality']['identifier'])
|
mdia = db.get('id', media['_id'])
|
||||||
finish = profile['finish'][nr]
|
mdia['status'] = 'done'
|
||||||
if finish:
|
mdia['last_edit'] = int(time.time())
|
||||||
mdia = db.get('id', media['_id'])
|
db.update(mdia)
|
||||||
mdia['status'] = 'done'
|
|
||||||
mdia['last_edit'] = int(time.time())
|
|
||||||
db.update(mdia)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error('Failed marking movie finished: %s', (traceback.format_exc()))
|
log.error('Failed marking movie finished: %s', (traceback.format_exc()))
|
||||||
@@ -459,18 +466,19 @@ class Renamer(Plugin):
|
|||||||
# When a release already exists
|
# When a release already exists
|
||||||
if release.get('status') == 'done':
|
if release.get('status') == 'done':
|
||||||
|
|
||||||
release_order = quality_order.index(release['quality'])
|
# This is where CP removes older, lesser quality releases or releases that are not wanted anymore
|
||||||
group_quality_order = quality_order.index(group['meta_data']['quality']['identifier'])
|
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 is_higher == 'higher':
|
||||||
if release_order > group_quality_order:
|
log.info('Removing lesser or not wanted quality %s for %s.', (media_title, release.get('quality')))
|
||||||
log.info('Removing lesser quality %s for %s.', (media_title, release.get('quality')))
|
|
||||||
for file_type in release.get('files', {}):
|
for file_type in release.get('files', {}):
|
||||||
for release_file in release['files'][file_type]:
|
for release_file in release['files'][file_type]:
|
||||||
remove_files.append(release_file)
|
remove_files.append(release_file)
|
||||||
remove_releases.append(release)
|
remove_releases.append(release)
|
||||||
|
|
||||||
# Same quality, but still downloaded, so maybe repack/proper/unrated/directors cut etc
|
# 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')))
|
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 file_type in release.get('files', {}):
|
||||||
for release_file in release['files'][file_type]:
|
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):
|
elif isinstance(release_download, dict):
|
||||||
# Tag download_files if they are known
|
# Tag download_files if they are known
|
||||||
if release_download.get('files', []):
|
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
|
# Tag all files in release folder
|
||||||
elif release_download['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])
|
tag_files.extend([os.path.join(root, name) for name in names])
|
||||||
|
|
||||||
for filename in tag_files:
|
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):
|
if isinstance(group, dict):
|
||||||
tag_files = [sorted(list(group['files']['movie']))[0]]
|
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):
|
if not group.get('dirname') or not os.path.isdir(folder):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif isinstance(release_download, dict):
|
elif isinstance(release_download, dict):
|
||||||
|
|
||||||
folder = release_download['folder']
|
folder = sp(release_download['folder'])
|
||||||
if not os.path.isdir(folder):
|
if not os.path.isdir(folder):
|
||||||
return False
|
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:
|
if not release_download:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
folder = release_download['folder']
|
folder = sp(release_download['folder'])
|
||||||
if not os.path.isdir(folder):
|
if not os.path.isdir(folder):
|
||||||
return False
|
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
|
return False
|
||||||
|
|
||||||
def moveFile(self, old, dest, forcemove = False):
|
def moveFile(self, old, dest, forcemove = False):
|
||||||
dest = ss(dest)
|
dest = sp(dest)
|
||||||
try:
|
try:
|
||||||
if forcemove or self.conf('file_action') not in ['copy', 'link']:
|
if forcemove or self.conf('file_action') not in ['copy', 'link']:
|
||||||
try:
|
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):
|
def replaceDoubles(self, string):
|
||||||
|
|
||||||
replaces = [
|
replaces = [
|
||||||
('\.+', '.'), ('_+', '_'), ('-+', '-'), ('\s+', ' '),
|
('\.+', '.'), ('_+', '_'), ('-+', '-'), ('\s+', ' '), (' \\\\', '\\\\'), (' /', '/'),
|
||||||
('(\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({
|
release_download.update({
|
||||||
'imdb_id': getIdentifier(media),
|
'imdb_id': getIdentifier(media),
|
||||||
'quality': rls['quality'],
|
'quality': rls['quality'],
|
||||||
|
'is_3d': rls['is_3d'],
|
||||||
'protocol': rls.get('info', {}).get('protocol') or rls.get('info', {}).get('type'),
|
'protocol': rls.get('info', {}).get('protocol') or rls.get('info', {}).get('type'),
|
||||||
'release_id': rls['_id'],
|
'release_id': rls['_id'],
|
||||||
})
|
})
|
||||||
@@ -1193,6 +1202,8 @@ rename_options = {
|
|||||||
'first': 'First letter (M)',
|
'first': 'First letter (M)',
|
||||||
'quality': 'Quality (720p)',
|
'quality': 'Quality (720p)',
|
||||||
'quality_type': '(HD) or (SD)',
|
'quality_type': '(HD) or (SD)',
|
||||||
|
'3d': '3D',
|
||||||
|
'3d_type': '3D Type (Full SBS)',
|
||||||
'video': 'Video (x264)',
|
'video': 'Video (x264)',
|
||||||
'audio': 'Audio (DTS)',
|
'audio': 'Audio (DTS)',
|
||||||
'group': 'Releasegroup name',
|
'group': 'Releasegroup name',
|
||||||
@@ -1205,7 +1216,8 @@ rename_options = {
|
|||||||
'imdb_id': 'IMDB id (tt0123456)',
|
'imdb_id': 'IMDB id (tt0123456)',
|
||||||
'cd': 'CD number (cd1)',
|
'cd': 'CD number (cd1)',
|
||||||
'cd_nr': 'Just the cd nr. (1)',
|
'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',
|
'category': 'Category label',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import traceback
|
|||||||
|
|
||||||
from couchpotato import get_db
|
from couchpotato import get_db
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
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, \
|
from couchpotato.core.helpers.variable import getExt, getImdb, tryInt, \
|
||||||
splitString, getIdentifier
|
splitString, getIdentifier
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -40,6 +40,17 @@ class Scanner(Plugin):
|
|||||||
'trailer': ['mov', 'mp4', 'flv']
|
'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 = {
|
file_types = {
|
||||||
'subtitle': ('subtitle', 'subtitle'),
|
'subtitle': ('subtitle', 'subtitle'),
|
||||||
'subtitle_extra': ('subtitle', 'subtitle_extra'),
|
'subtitle_extra': ('subtitle', 'subtitle_extra'),
|
||||||
@@ -60,33 +71,43 @@ class Scanner(Plugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
codecs = {
|
codecs = {
|
||||||
'audio': ['dts', 'ac3', 'ac3d', 'mp3'],
|
'audio': ['DTS', 'AC3', 'AC3D', 'MP3'],
|
||||||
'video': ['x264', 'h264', 'divx', 'xvid']
|
'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 = {
|
audio_codec_map = {
|
||||||
0x2000: 'ac3',
|
0x2000: 'AC3',
|
||||||
0x2001: 'dts',
|
0x2001: 'DTS',
|
||||||
0x0055: 'mp3',
|
0x0055: 'MP3',
|
||||||
0x0050: 'mp2',
|
0x0050: 'MP2',
|
||||||
0x0001: 'pcm',
|
0x0001: 'PCM',
|
||||||
0x003: 'pcm',
|
0x003: 'WAV',
|
||||||
0x77a1: 'tta1',
|
0x77a1: 'TTA1',
|
||||||
0x5756: 'wav',
|
0x5756: 'WAV',
|
||||||
0x6750: 'vorbis',
|
0x6750: 'Vorbis',
|
||||||
0xF1AC: 'flac',
|
0xF1AC: 'FLAC',
|
||||||
0x00ff: 'aac',
|
0x00ff: 'AAC',
|
||||||
}
|
}
|
||||||
|
|
||||||
source_media = {
|
source_media = {
|
||||||
'bluray': ['bluray', 'blu-ray', 'brrip', 'br-rip'],
|
'Blu-ray': ['bluray', 'blu-ray', 'brrip', 'br-rip'],
|
||||||
'hddvd': ['hddvd', 'hd-dvd'],
|
'HD DVD': ['hddvd', 'hd-dvd'],
|
||||||
'dvd': ['dvd'],
|
'DVD': ['dvd'],
|
||||||
'hdtv': ['hdtv']
|
'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' \
|
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|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]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
|
'|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 = [
|
multipart_regex = [
|
||||||
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
||||||
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
||||||
@@ -130,7 +151,7 @@ class Scanner(Plugin):
|
|||||||
try:
|
try:
|
||||||
files = []
|
files = []
|
||||||
for root, dirs, walk_files in scandir.walk(folder, followlinks=True):
|
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
|
# Break if CP wants to shut down
|
||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
@@ -164,7 +185,7 @@ class Scanner(Plugin):
|
|||||||
identifiers = [identifier]
|
identifiers = [identifier]
|
||||||
|
|
||||||
# Identifier with quality
|
# 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:
|
if quality:
|
||||||
identifier_with_quality = '%s %s' % (identifier, quality.get('identifier', ''))
|
identifier_with_quality = '%s %s' % (identifier, quality.get('identifier', ''))
|
||||||
identifiers = [identifier_with_quality, identifier]
|
identifiers = [identifier_with_quality, identifier]
|
||||||
@@ -431,28 +452,39 @@ class Scanner(Plugin):
|
|||||||
for cur_file in files:
|
for cur_file in files:
|
||||||
if not self.filesizeBetween(cur_file, self.file_sizes['movie']): continue # Ignore smaller 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:
|
try:
|
||||||
data['video'] = meta.get('video', self.getCodec(cur_file, self.codecs['video']))
|
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'] = meta.get('audio', self.getCodec(cur_file, self.codecs['audio']))
|
||||||
data['resolution_width'] = meta.get('resolution_width', 720)
|
data['audio_channels'] = meta.get('audio_channels', 2.0)
|
||||||
data['resolution_height'] = meta.get('resolution_height', 480)
|
if meta.get('resolution_width'):
|
||||||
data['audio_channels'] = meta.get('audio_channels', 2.0)
|
data['resolution_width'] = meta.get('resolution_width')
|
||||||
data['aspect'] = round(float(meta.get('resolution_width', 720)) / meta.get('resolution_height', 480), 2)
|
data['resolution_height'] = meta.get('resolution_height')
|
||||||
except:
|
data['aspect'] = round(float(meta.get('resolution_width')) / meta.get('resolution_height', 1), 2)
|
||||||
log.debug('Error parsing metadata: %s %s', (cur_file, traceback.format_exc()))
|
else:
|
||||||
pass
|
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
|
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'):
|
if release_download and release_download.get('quality'):
|
||||||
data['quality'] = fireEvent('quality.single', release_download.get('quality'), single = True)
|
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']:
|
if not data['quality']:
|
||||||
data['quality'] = fireEvent('quality.guess', files = files, extra = data, single = True)
|
data['quality'] = quality
|
||||||
|
|
||||||
if not data['quality']:
|
if not data['quality']:
|
||||||
data['quality'] = fireEvent('quality.single', 'dvdr' if group['is_dvd'] else 'dvdrip', single = True)
|
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])
|
filename = re.sub('(.cp\(tt[0-9{7}]+\))', '', files[0])
|
||||||
data['group'] = self.getGroup(filename[len(folder):])
|
data['group'] = self.getGroup(filename[len(folder):])
|
||||||
data['source'] = self.getSourceMedia(filename)
|
data['source'] = self.getSourceMedia(filename)
|
||||||
|
if data['quality'].get('is_3d', 0):
|
||||||
|
data['3d_type'] = self.get3dType(filename)
|
||||||
return data
|
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):
|
def getMeta(self, filename):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
p = enzyme.parse(filename)
|
p = enzyme.parse(filename)
|
||||||
|
|
||||||
# Video codec
|
# 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
|
# Audio codec
|
||||||
ac = p.audio[0].codec
|
ac = p.audio[0].codec
|
||||||
@@ -708,19 +756,26 @@ class Scanner(Plugin):
|
|||||||
if not file_size: file_size = []
|
if not file_size: file_size = []
|
||||||
|
|
||||||
try:
|
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:
|
except:
|
||||||
log.error('Couldn\'t get filesize of %s.', file)
|
log.error('Couldn\'t get filesize of %s.', file)
|
||||||
|
|
||||||
return False
|
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 = file_path.replace(folder, '').lstrip(os.path.sep) # root folder
|
||||||
identifier = os.path.splitext(identifier)[0] # ext
|
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:
|
try:
|
||||||
path_split = splitString(identifier, os.path.sep)
|
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
|
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
|
# remove cptag
|
||||||
identifier = self.removeCPTag(identifier)
|
identifier = self.removeCPTag(identifier)
|
||||||
|
|
||||||
# groups, release tags, scenename cleaner, regex isn't correct
|
# simplify the string
|
||||||
identifier = re.sub(self.clean, '::', simplifyString(identifier)).strip(':')
|
identifier = simplifyString(identifier)
|
||||||
|
|
||||||
|
year = self.findYear(file_path)
|
||||||
|
|
||||||
|
# groups, release tags, scenename cleaner
|
||||||
|
identifier = re.sub(self.clean, '::', identifier).strip(':')
|
||||||
|
|
||||||
# Year
|
# Year
|
||||||
if year and identifier[:4] != year:
|
if year and identifier[:4] != year:
|
||||||
@@ -785,6 +845,14 @@ class Scanner(Plugin):
|
|||||||
except:
|
except:
|
||||||
return ''
|
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):
|
def getGroup(self, file):
|
||||||
try:
|
try:
|
||||||
match = re.findall('\-([A-Z0-9]+)[\.\/]', file, re.I)
|
match = re.findall('\-([A-Z0-9]+)[\.\/]', file, re.I)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class Subtitle(Plugin):
|
|||||||
|
|
||||||
for lang in self.getLanguages():
|
for lang in self.getLanguages():
|
||||||
if lang not in available_languages:
|
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:
|
for subtitle in download:
|
||||||
downloaded.extend(download[subtitle])
|
downloaded.extend(download[subtitle])
|
||||||
|
|
||||||
@@ -72,6 +72,14 @@ config = [{
|
|||||||
'name': 'languages',
|
'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>'),
|
'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,
|
Extends: PageBase,
|
||||||
|
|
||||||
|
order: 80,
|
||||||
name: 'userscript',
|
name: 'userscript',
|
||||||
has_tab: false,
|
has_tab: false,
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ Page.Wizard = new Class({
|
|||||||
|
|
||||||
Extends: Page.Settings,
|
Extends: Page.Settings,
|
||||||
|
|
||||||
|
order: 70,
|
||||||
name: 'wizard',
|
name: 'wizard',
|
||||||
has_tab: false,
|
has_tab: false,
|
||||||
wizard_only: true,
|
wizard_only: true,
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ class Settings(object):
|
|||||||
propert = db.get('property', identifier, with_doc = True)
|
propert = db.get('property', identifier, with_doc = True)
|
||||||
prop = propert['doc']['value']
|
prop = propert['doc']['value']
|
||||||
except:
|
except:
|
||||||
self.log.debug('Property "%s" doesn\'t exist: %s', (identifier, traceback.format_exc(0)))
|
pass # self.log.debug('Property "%s" doesn\'t exist: %s', (identifier, traceback.format_exc(0)))
|
||||||
|
|
||||||
return prop
|
return prop
|
||||||
|
|
||||||
|
|||||||
+20
-14
@@ -10,20 +10,20 @@ import warnings
|
|||||||
import re
|
import re
|
||||||
import tarfile
|
import tarfile
|
||||||
|
|
||||||
from CodernityDB.database_thread_safe import ThreadSafeDatabase
|
from CodernityDB.database_super_thread_safe import SuperThreadSafeDatabase
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from cache import FileSystemCache
|
from cache import FileSystemCache
|
||||||
from couchpotato import KeyHandler, LoginHandler, LogoutHandler
|
from couchpotato import KeyHandler, LoginHandler, LogoutHandler
|
||||||
from couchpotato.api import NonBlockHandler, ApiHandler
|
from couchpotato.api import NonBlockHandler, ApiHandler
|
||||||
from couchpotato.core.event import fireEventAsync, fireEvent
|
from couchpotato.core.event import fireEventAsync, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import sp
|
||||||
from couchpotato.core.helpers.variable import getDataDir, tryInt
|
from couchpotato.core.helpers.variable import getDataDir, tryInt
|
||||||
from scandir import scandir
|
from scandir import scandir
|
||||||
from tornado.httpserver import HTTPServer
|
from tornado.httpserver import HTTPServer
|
||||||
from tornado.web import Application, StaticFileHandler, RedirectHandler
|
from tornado.web import Application, StaticFileHandler, RedirectHandler
|
||||||
|
|
||||||
|
|
||||||
def getOptions(base_path, args):
|
def getOptions(args):
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
parser = ArgumentParser(prog = 'CouchPotato.py')
|
parser = ArgumentParser(prog = 'CouchPotato.py')
|
||||||
@@ -86,15 +86,15 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
Env.set('encoding', encoding)
|
Env.set('encoding', encoding)
|
||||||
|
|
||||||
# Do db stuff
|
# Do db stuff
|
||||||
db_path = toUnicode(os.path.join(data_dir, 'database'))
|
db_path = sp(os.path.join(data_dir, 'database'))
|
||||||
|
|
||||||
# Check if database exists
|
# Check if database exists
|
||||||
db = ThreadSafeDatabase(db_path)
|
db = SuperThreadSafeDatabase(db_path)
|
||||||
db_exists = db.exists()
|
db_exists = db.exists()
|
||||||
if db_exists:
|
if db_exists:
|
||||||
|
|
||||||
# Backup before start and cleanup old backups
|
# Backup before start and cleanup old backups
|
||||||
backup_path = toUnicode(os.path.join(data_dir, 'db_backup'))
|
backup_path = sp(os.path.join(data_dir, 'db_backup'))
|
||||||
backup_count = 5
|
backup_count = 5
|
||||||
existing_backups = []
|
existing_backups = []
|
||||||
if not os.path.isdir(backup_path): os.makedirs(backup_path)
|
if not os.path.isdir(backup_path): os.makedirs(backup_path)
|
||||||
@@ -114,7 +114,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
os.remove(os.path.join(backup_path, eb[1]))
|
os.remove(os.path.join(backup_path, eb[1]))
|
||||||
|
|
||||||
# Create new backup
|
# Create new backup
|
||||||
new_backup = toUnicode(os.path.join(backup_path, '%s.tar.gz' % int(time.time())))
|
new_backup = sp(os.path.join(backup_path, '%s.tar.gz' % int(time.time())))
|
||||||
zipf = tarfile.open(new_backup, 'w:gz')
|
zipf = tarfile.open(new_backup, 'w:gz')
|
||||||
for root, dirs, files in scandir.walk(db_path):
|
for root, dirs, files in scandir.walk(db_path):
|
||||||
for zfilename in files:
|
for zfilename in files:
|
||||||
@@ -128,12 +128,12 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
db.create()
|
db.create()
|
||||||
|
|
||||||
# Register environment settings
|
# Register environment settings
|
||||||
Env.set('app_dir', toUnicode(base_path))
|
Env.set('app_dir', sp(base_path))
|
||||||
Env.set('data_dir', toUnicode(data_dir))
|
Env.set('data_dir', sp(data_dir))
|
||||||
Env.set('log_path', toUnicode(os.path.join(log_dir, 'CouchPotato.log')))
|
Env.set('log_path', sp(os.path.join(log_dir, 'CouchPotato.log')))
|
||||||
Env.set('db', db)
|
Env.set('db', db)
|
||||||
Env.set('cache_dir', toUnicode(os.path.join(data_dir, 'cache')))
|
Env.set('cache_dir', sp(os.path.join(data_dir, 'cache')))
|
||||||
Env.set('cache', FileSystemCache(toUnicode(os.path.join(Env.get('cache_dir'), 'python'))))
|
Env.set('cache', FileSystemCache(sp(os.path.join(Env.get('cache_dir'), 'python'))))
|
||||||
Env.set('console_log', options.console_log)
|
Env.set('console_log', options.console_log)
|
||||||
Env.set('quiet', options.quiet)
|
Env.set('quiet', options.quiet)
|
||||||
Env.set('desktop', desktop)
|
Env.set('desktop', desktop)
|
||||||
@@ -245,13 +245,13 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
static_path = '%sstatic/' % web_base
|
static_path = '%sstatic/' % web_base
|
||||||
for dir_name in ['fonts', 'images', 'scripts', 'style']:
|
for dir_name in ['fonts', 'images', 'scripts', 'style']:
|
||||||
application.add_handlers(".*$", [
|
application.add_handlers(".*$", [
|
||||||
('%s%s/(.*)' % (static_path, dir_name), StaticFileHandler, {'path': toUnicode(os.path.join(base_path, 'couchpotato', 'static', dir_name))})
|
('%s%s/(.*)' % (static_path, dir_name), StaticFileHandler, {'path': sp(os.path.join(base_path, 'couchpotato', 'static', dir_name))})
|
||||||
])
|
])
|
||||||
Env.set('static_path', static_path)
|
Env.set('static_path', static_path)
|
||||||
|
|
||||||
# Load configs & plugins
|
# Load configs & plugins
|
||||||
loader = Env.get('loader')
|
loader = Env.get('loader')
|
||||||
loader.preload(root = toUnicode(base_path))
|
loader.preload(root = sp(base_path))
|
||||||
loader.run()
|
loader.run()
|
||||||
|
|
||||||
# Fill database with needed stuff
|
# Fill database with needed stuff
|
||||||
@@ -262,8 +262,14 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
|
|
||||||
# Go go go!
|
# Go go go!
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
|
from tornado.autoreload import add_reload_hook
|
||||||
loop = IOLoop.current()
|
loop = IOLoop.current()
|
||||||
|
|
||||||
|
# Reload hook
|
||||||
|
def test():
|
||||||
|
fireEvent('app.shutdown')
|
||||||
|
add_reload_hook(test)
|
||||||
|
|
||||||
# Some logging and fire load event
|
# Some logging and fire load event
|
||||||
try: log.info('Starting server on port %(port)s', config)
|
try: log.info('Starting server on port %(port)s', config)
|
||||||
except: pass
|
except: pass
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var CouchPotato = new Class({
|
var CouchPotato = new Class({
|
||||||
|
|
||||||
Implements: [Events, Options],
|
Implements: [Events, Options],
|
||||||
|
|
||||||
@@ -137,19 +137,34 @@
|
|||||||
createPages: function(){
|
createPages: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
var pages = [];
|
||||||
Object.each(Page, function(page_class, class_name){
|
Object.each(Page, function(page_class, class_name){
|
||||||
var pg = new Page[class_name](self, {});
|
var pg = new Page[class_name](self, {});
|
||||||
self.pages[class_name] = pg;
|
self.pages[class_name] = pg;
|
||||||
|
|
||||||
self.fireEvent('load'+class_name);
|
pages.include({
|
||||||
|
'order': pg.order,
|
||||||
$(pg).inject(self.pages);
|
'name': class_name,
|
||||||
|
'class': pg
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pages.stableSort(self.sortPageByOrder).each(function(page){
|
||||||
|
page['class'].load();
|
||||||
|
self.fireEvent('load'+page.name);
|
||||||
|
$(page['class']).inject(self.content);
|
||||||
|
});
|
||||||
|
|
||||||
|
delete pages;
|
||||||
|
|
||||||
self.fireEvent('load');
|
self.fireEvent('load');
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sortPageByOrder: function(a, b){
|
||||||
|
return (a.order || 100) - (b.order || 100)
|
||||||
|
},
|
||||||
|
|
||||||
openPage: function(url) {
|
openPage: function(url) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ var PageBase = new Class({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
order: 1,
|
||||||
has_tab: true,
|
has_tab: true,
|
||||||
name: '',
|
name: '',
|
||||||
|
|
||||||
@@ -16,6 +17,10 @@ var PageBase = new Class({
|
|||||||
|
|
||||||
// Create main page container
|
// Create main page container
|
||||||
self.el = new Element('div.page.'+self.name);
|
self.el = new Element('div.page.'+self.name);
|
||||||
|
},
|
||||||
|
|
||||||
|
load: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
// Create tab for page
|
// Create tab for page
|
||||||
if(self.has_tab){
|
if(self.has_tab){
|
||||||
@@ -26,6 +31,7 @@ var PageBase = new Class({
|
|||||||
'text': self.name.capitalize()
|
'text': self.name.capitalize()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
open: function(action, params){
|
open: function(action, params){
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ Page.Home = new Class({
|
|||||||
|
|
||||||
// Make all thumbnails the same size
|
// Make all thumbnails the same size
|
||||||
self.soon_list.addEvent('loaded', function(){
|
self.soon_list.addEvent('loaded', function(){
|
||||||
var images = $(self.soon_list).getElements('.poster'),
|
var images = $(self.soon_list).getElements('.poster, .no_thumbnail'),
|
||||||
timer,
|
timer,
|
||||||
highest = 100;
|
highest = 100;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ Page.Settings = new Class({
|
|||||||
|
|
||||||
Extends: PageBase,
|
Extends: PageBase,
|
||||||
|
|
||||||
|
order: 50,
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
title: 'Change settings.',
|
title: 'Change settings.',
|
||||||
wizard_only: false,
|
wizard_only: false,
|
||||||
@@ -112,7 +113,7 @@ Page.Settings = new Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
sortByOrder: function(a, b){
|
sortByOrder: function(a, b){
|
||||||
return (a.order || 100) - (b.order || 100)
|
return (a.order || 100) - (b.order || 100)
|
||||||
},
|
},
|
||||||
|
|
||||||
create: function(json){
|
create: function(json){
|
||||||
|
|||||||
Reference in new issue
Block a user