Browse Source

Merge branch 'refs/heads/develop' into tv

pull/3111/head
Ruud 11 years ago
parent
commit
7410288781
  1. 1
      couchpotato/__init__.py
  2. 2
      couchpotato/core/_base/_core.py
  3. 9
      couchpotato/core/_base/clientscript.py
  4. 6
      couchpotato/core/_base/updater/main.py
  5. 4
      couchpotato/core/_base/updater/static/updater.js
  6. 39
      couchpotato/core/database.py
  7. 11
      couchpotato/core/downloaders/base.py
  8. 2
      couchpotato/core/downloaders/nzbget.py
  9. 24
      couchpotato/core/downloaders/qbittorrent_.py
  10. 51
      couchpotato/core/downloaders/rtorrent_.py
  11. 1
      couchpotato/core/downloaders/transmission.py
  12. 16
      couchpotato/core/downloaders/utorrent.py
  13. 3
      couchpotato/core/helpers/request.py
  14. 7
      couchpotato/core/helpers/variable.py
  15. 13
      couchpotato/core/loader.py
  16. 1
      couchpotato/core/media/__init__.py
  17. 1
      couchpotato/core/media/_base/library/__init__.py
  18. 1
      couchpotato/core/media/_base/matcher/__init__.py
  19. 10
      couchpotato/core/media/_base/media/index.py
  20. 18
      couchpotato/core/media/_base/media/main.py
  21. 10
      couchpotato/core/media/_base/providers/base.py
  22. 8
      couchpotato/core/media/_base/providers/nzb/binsearch.py
  23. 4
      couchpotato/core/media/_base/providers/nzb/nzbclub.py
  24. 2
      couchpotato/core/media/_base/providers/nzb/nzbindex.py
  25. 4
      couchpotato/core/media/_base/providers/nzb/omgwtfnzbs.py
  26. 6
      couchpotato/core/media/_base/providers/torrent/bithdtv.py
  27. 6
      couchpotato/core/media/_base/providers/torrent/bitsoup.py
  28. 4
      couchpotato/core/media/_base/providers/torrent/hdbits.py
  29. 12
      couchpotato/core/media/_base/providers/torrent/ilovetorrents.py
  30. 20
      couchpotato/core/media/_base/providers/torrent/iptorrents.py
  31. 4
      couchpotato/core/media/_base/providers/torrent/kickasstorrents.py
  32. 36
      couchpotato/core/media/_base/providers/torrent/passthepopcorn.py
  33. 2
      couchpotato/core/media/_base/providers/torrent/publichd.py
  34. 18
      couchpotato/core/media/_base/providers/torrent/sceneaccess.py
  35. 4
      couchpotato/core/media/_base/providers/torrent/thepiratebay.py
  36. 8
      couchpotato/core/media/_base/providers/torrent/torrentbytes.py
  37. 2
      couchpotato/core/media/_base/providers/torrent/torrentday.py
  38. 14
      couchpotato/core/media/_base/providers/torrent/torrentleech.py
  39. 10
      couchpotato/core/media/_base/providers/torrent/torrentshack.py
  40. 128
      couchpotato/core/media/_base/providers/torrent/torrentz.py
  41. 4
      couchpotato/core/media/_base/providers/torrent/yify.py
  42. 46
      couchpotato/core/media/_base/search/static/search.css
  43. 46
      couchpotato/core/media/_base/search/static/search.js
  44. 15
      couchpotato/core/media/_base/searcher/main.py
  45. 60
      couchpotato/core/media/movie/_base/static/list.js
  46. 42
      couchpotato/core/media/movie/_base/static/movie.actions.js
  47. 5
      couchpotato/core/media/movie/_base/static/movie.css
  48. 52
      couchpotato/core/media/movie/_base/static/movie.js
  49. 18
      couchpotato/core/media/movie/_base/static/search.js
  50. 34
      couchpotato/core/media/movie/charts/__init__.py
  51. 58
      couchpotato/core/media/movie/charts/main.py
  52. 261
      couchpotato/core/media/movie/charts/static/charts.css
  53. 170
      couchpotato/core/media/movie/charts/static/charts.js
  54. 13
      couchpotato/core/media/movie/providers/automation/base.py
  55. 44
      couchpotato/core/media/movie/providers/automation/bluray.py
  56. 5
      couchpotato/core/media/movie/providers/automation/goodfilms.py
  57. 85
      couchpotato/core/media/movie/providers/automation/imdb.py
  58. 5
      couchpotato/core/media/movie/providers/automation/letterboxd.py
  59. 3
      couchpotato/core/media/movie/providers/info/_modifier.py
  60. 1
      couchpotato/core/media/movie/providers/info/themoviedb.py
  61. 2
      couchpotato/core/media/movie/providers/torrent/bithdtv.py
  62. 3
      couchpotato/core/media/movie/providers/torrent/bitsoup.py
  63. 3
      couchpotato/core/media/movie/providers/torrent/iptorrents.py
  64. 44
      couchpotato/core/media/movie/providers/torrent/passthepopcorn.py
  65. 6
      couchpotato/core/media/movie/providers/torrent/sceneaccess.py
  66. 9
      couchpotato/core/media/movie/providers/torrent/thepiratebay.py
  67. 1
      couchpotato/core/media/movie/providers/torrent/torrentday.py
  68. 2
      couchpotato/core/media/movie/providers/torrent/torrentleech.py
  69. 2
      couchpotato/core/media/movie/providers/torrent/torrentshack.py
  70. 15
      couchpotato/core/media/movie/providers/torrent/torrentz.py
  71. 2
      couchpotato/core/media/movie/providers/trailer/hdtrailers.py
  72. 8
      couchpotato/core/media/movie/providers/userscript/filmcentrum.py
  73. 17
      couchpotato/core/media/movie/searcher.py
  74. 12
      couchpotato/core/media/movie/suggestion/static/suggest.css
  75. 13
      couchpotato/core/media/movie/suggestion/static/suggest.js
  76. 2
      couchpotato/core/notifications/core/main.py
  77. 26
      couchpotato/core/notifications/core/static/notification.js
  78. 3
      couchpotato/core/notifications/email_.py
  79. 2
      couchpotato/core/notifications/growl.py
  80. 1
      couchpotato/core/notifications/nmj.py
  81. 2
      couchpotato/core/notifications/pushalot.py
  82. 13
      couchpotato/core/notifications/pushover.py
  83. 2
      couchpotato/core/notifications/twitter/static/twitter.js
  84. 2
      couchpotato/core/notifications/xbmc.py
  85. 4
      couchpotato/core/plugins/automation.py
  86. 7
      couchpotato/core/plugins/base.py
  87. 3
      couchpotato/core/plugins/browser.py
  88. 6
      couchpotato/core/plugins/category/static/category.css
  89. 26
      couchpotato/core/plugins/category/static/category.js
  90. 4
      couchpotato/core/plugins/dashboard.py
  91. 3
      couchpotato/core/plugins/file.py
  92. 6
      couchpotato/core/plugins/log/main.py
  93. 2
      couchpotato/core/plugins/log/static/log.css
  94. 7
      couchpotato/core/plugins/log/static/log.js
  95. 8
      couchpotato/core/plugins/manage.py
  96. 17
      couchpotato/core/plugins/profile/main.py
  97. 54
      couchpotato/core/plugins/profile/static/profile.css
  98. 67
      couchpotato/core/plugins/profile/static/profile.js
  99. 60
      couchpotato/core/plugins/quality/main.py
  100. 18
      couchpotato/core/plugins/quality/static/quality.js

1
couchpotato/__init__.py

@ -15,6 +15,7 @@ log = CPLog(__name__)
views = {}
template_loader = template.Loader(os.path.join(os.path.dirname(__file__), 'templates'))
class BaseHandler(RequestHandler):
def get_current_user(self):

2
couchpotato/core/_base/_core.py

@ -189,7 +189,7 @@ class Core(Plugin):
def signalHandler(self):
if Env.get('daemonized'): return
def signal_handler(signal, frame):
def signal_handler(*args, **kwargs):
fireEvent('app.shutdown', single = True)
signal.signal(signal.SIGINT, signal_handler)

9
couchpotato/core/_base/clientscript.py

@ -57,13 +57,12 @@ class ClientScript(Plugin):
],
}
urls = {'style': {}, 'script': {}}
minified = {'style': {}, 'script': {}}
paths = {'style': {}, 'script': {}}
comment = {
'style': '/*** %s:%d ***/\n',
'script': '// %s:%d\n'
'style': '/*** %s:%d ***/\n',
'script': '// %s:%d\n'
}
html = {
@ -95,7 +94,6 @@ class ClientScript(Plugin):
else:
self.registerStyle(core_url, file_path, position = 'front')
def minify(self):
# Create cache dir
@ -129,7 +127,7 @@ class ClientScript(Plugin):
data = cssmin(data)
data = data.replace('../images/', '../static/images/')
data = data.replace('../fonts/', '../static/fonts/')
data = data.replace('../../static/', '../static/') # Replace inside plugins
data = data.replace('../../static/', '../static/') # Replace inside plugins
raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data})
@ -192,6 +190,7 @@ class ClientScript(Plugin):
prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow']
prefix_tags = ['ms', 'moz', 'webkit']
def prefix(self, data):
trimmed_data = re.sub('(\t|\n|\r)+', '', data)

6
couchpotato/core/_base/updater/main.py

@ -164,14 +164,14 @@ class BaseUpdater(Plugin):
def info(self):
version = self.getVersion()
current_version = self.getVersion()
return {
'last_check': self.last_check,
'update_version': self.update_version,
'version': version,
'version': current_version,
'repo_name': '%s/%s' % (self.repo_user, self.repo_name),
'branch': version.get('branch', self.branch),
'branch': current_version.get('branch', self.branch),
}
def getVersion(self):

4
couchpotato/core/_base/updater/static/updater.js

@ -5,7 +5,7 @@ var UpdaterBase = new Class({
initialize: function(){
var self = this;
App.addEvent('load', self.info.bind(self, 2000))
App.addEvent('load', self.info.bind(self, 2000));
App.addEvent('unload', function(){
if(self.timer)
clearTimeout(self.timer);
@ -66,7 +66,7 @@ var UpdaterBase = new Class({
var changelog = 'https://github.com/'+data.repo_name+'/compare/'+data.version.hash+'...'+data.branch;
if(data.update_version.changelog)
changelog = data.update_version.changelog + '#' + data.version.hash+'...'+data.update_version.hash
changelog = data.update_version.changelog + '#' + data.version.hash+'...'+data.update_version.hash;
self.message = new Element('div.message.update').adopt(
new Element('span', {

39
couchpotato/core/database.py

@ -21,6 +21,8 @@ class Database(object):
def __init__(self):
addApiView('database.list_documents', self.listDocuments)
addApiView('database.reindex', self.reindex)
addApiView('database.compact', self.compact)
addApiView('database.document.update', self.updateDocument)
addApiView('database.document.delete', self.deleteDocument)
@ -114,9 +116,36 @@ class Database(object):
results[key] = []
results[key].append(document)
return results
def reindex(self, **kwargs):
success = True
try:
db = self.getDB()
db.reindex()
except:
log.error('Failed index: %s', traceback.format_exc())
success = False
return {
'success': success
}
def compact(self, **kwargs):
success = True
try:
db = self.getDB()
db.compact()
except:
log.error('Failed compact: %s', traceback.format_exc())
success = False
return {
'success': success
}
def migrate(self):
from couchpotato import Env
@ -181,7 +210,7 @@ class Database(object):
migrate_data[ml][p[0]] = [migrate_data[ml][p[0]]]
migrate_data[ml][p[0]].append(columns)
c.close()
conn.close()
log.info('Getting data took %s', time.time() - migrate_start)
@ -297,6 +326,11 @@ class Database(object):
releaseinfos = migrate_data['releaseinfo']
for x in releaseinfos:
info = releaseinfos[x]
# Skip if release doesn't exist for this info
if not migrate_data['release'].get(info.get('release_id')):
continue
if not migrate_data['release'][info.get('release_id')].get('info'):
migrate_data['release'][info.get('release_id')]['info'] = {}
@ -438,7 +472,6 @@ class Database(object):
log.info('Total migration took %s', time.time() - migrate_start)
log.info('=' * 30)
# rename old database
log.info('Renaming old database to %s ', old_db + '.old')
os.rename(old_db, old_db + '.old')

11
couchpotato/core/downloaders/base.py

@ -24,17 +24,21 @@ class Downloader(Provider):
]
torrent_trackers = [
'http://tracker.publicbt.com/announce',
'udp://tracker.istole.it:80/announce',
'udp://fr33domtracker.h33t.com:3310/announce',
'http://tracker.istole.it/announce',
'http://tracker.ccc.de/announce',
'udp://fr33domtracker.h33t.com:3310/announce',
'http://tracker.publicbt.com/announce',
'udp://tracker.publicbt.com:80/announce',
'http://tracker.ccc.de/announce',
'udp://tracker.ccc.de:80/announce',
'http://exodus.desync.com/announce',
'http://exodus.desync.com:6969/announce',
'http://tracker.publichd.eu/announce',
'udp://tracker.publichd.eu:80/announce',
'http://tracker.openbittorrent.com/announce',
'udp://tracker.openbittorrent.com/announce',
'udp://tracker.openbittorrent.com:80/announce',
'udp://open.demonii.com:1337/announce',
]
def __init__(self):
@ -184,6 +188,7 @@ class Downloader(Provider):
def pause(self, release_download, pause):
return
class ReleaseDownloadList(list):
provider = None

2
couchpotato/core/downloaders/nzbget.py

@ -146,7 +146,7 @@ class NZBGet(Downloader):
'timeleft': timeleft,
})
for nzb in queue: # 'Parameters' is not passed in rpc.postqueue
for nzb in queue: # 'Parameters' is not passed in rpc.postqueue
if nzb['NZBID'] in ids:
log.debug('Found %s in NZBGet postprocessing queue', nzb['NZBFilename'])
release_downloads.append({

24
couchpotato/core/downloaders/qbittorrent_.py

@ -14,6 +14,7 @@ log = CPLog(__name__)
autoload = 'qBittorrent'
class qBittorrent(Downloader):
protocol = ['torrent', 'torrent_magnet']
@ -107,25 +108,24 @@ class qBittorrent(Downloader):
for torrent in torrents:
if torrent.hash in ids:
torrent.update_general() # get extra info
torrent_filelist = torrent.get_files()
torrent_files = []
t_files = torrent.get_files()
torrent_dir = os.path.join(torrent.save_path, torrent.name)
check_dir = os.path.join(torrent.save_path, torrent.name)
if os.path.isdir(check_dir):
torrent.save_path = check_dir
if os.path.isdir(torrent_dir):
torrent.save_path = torrent_dir
if len(t_files) > 1 and os.path.isdir(torrent.save_path): # multi file torrent
if len(torrent_filelist) > 1 and os.path.isdir(torrent_dir): # multi file torrent, path.isdir check makes sure we're not in the root download folder
for root, _, files in os.walk(torrent.save_path):
for f in files:
p = os.path.join(root, f)
if os.path.isfile(p):
torrent_files.append(sp(p))
torrent_files.append(sp(os.path.join(root, f)))
else: # multi or single file placed directly in torrent.save_path
for f in t_files:
p = os.path.join(torrent.save_path, f.name)
if os.path.isfile(p):
torrent_files.append(sp(p))
for f in torrent_filelist:
file_path = os.path.join(torrent.save_path, f.name)
if os.path.isfile(file_path):
torrent_files.append(sp(file_path))
release_downloads.append({
'id': torrent.hash,

51
couchpotato/core/downloaders/rtorrent_.py

@ -78,10 +78,10 @@ class rTorrent(Downloader):
self.error_msg = ''
try:
self.rt._verify_conn()
self.rt._verify_conn()
except AssertionError as e:
self.error_msg = e.message
self.rt = None
self.error_msg = e.message
self.rt = None
return self.rt
@ -94,44 +94,6 @@ class rTorrent(Downloader):
return False
def updateProviderGroup(self, name, data):
if data.get('seed_time'):
log.info('seeding time ignored, not supported')
if not name:
return False
if not self.connect():
return False
views = self.rt.get_views()
if name not in views:
self.rt.create_group(name)
group = self.rt.get_group(name)
try:
if data.get('seed_ratio'):
ratio = int(float(data.get('seed_ratio')) * 100)
log.debug('Updating provider ratio to %s, group name: %s', (ratio, name))
# Explicitly set all group options to ensure it is setup correctly
group.set_upload('1M')
group.set_min(ratio)
group.set_max(ratio)
group.set_command('d.stop')
group.enable()
else:
# Reset group action and disable it
group.set_command()
group.disable()
except MethodError as err:
log.error('Unable to set group options: %s', err.msg)
return False
return True
def download(self, data = None, media = None, filedata = None):
if not media: media = {}
@ -142,10 +104,6 @@ class rTorrent(Downloader):
if not self.connect():
return False
group_name = 'cp_' + data.get('provider').lower()
if not self.updateProviderGroup(group_name, data):
return False
torrent_params = {}
if self.conf('label'):
torrent_params['label'] = self.conf('label')
@ -186,9 +144,6 @@ class rTorrent(Downloader):
if self.conf('directory'):
torrent.set_directory(self.conf('directory'))
# Set Ratio Group
torrent.set_visible(group_name)
# Start torrent
if not self.conf('paused', default = 0):
torrent.start()

1
couchpotato/core/downloaders/transmission.py

@ -160,6 +160,7 @@ class Transmission(Downloader):
log.debug('Requesting Transmission to remove the torrent %s%s.', (release_download['name'], ' and cleanup the downloaded files' if delete_files else ''))
return self.trpc.remove_torrent(release_download['id'], delete_files)
class TransmissionRPC(object):
"""TransmissionRPC lite library"""

16
couchpotato/core/downloaders/utorrent.py

@ -29,14 +29,14 @@ class uTorrent(Downloader):
protocol = ['torrent', 'torrent_magnet']
utorrent_api = None
status_flags = {
'STARTED' : 1,
'CHECKING' : 2,
'CHECK-START' : 4,
'CHECKED' : 8,
'ERROR' : 16,
'PAUSED' : 32,
'QUEUED' : 64,
'LOADED' : 128
'STARTED': 1,
'CHECKING': 2,
'CHECK-START': 4,
'CHECKED': 8,
'ERROR': 16,
'PAUSED': 32,
'QUEUED': 64,
'LOADED': 128
}
def connect(self):

3
couchpotato/core/helpers/request.py

@ -2,7 +2,7 @@ from urllib import unquote
import re
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import natsortKey, tryInt
from couchpotato.core.helpers.variable import natsortKey
def getParams(params):
@ -44,6 +44,7 @@ def getParams(params):
return dictToList(temp)
non_decimal = re.compile(r'[^\d.]+')
def dictToList(params):
if type(params) is dict:

7
couchpotato/core/helpers/variable.py

@ -216,6 +216,7 @@ def tryFloat(s):
return float(s)
except: return 0
def natsortKey(string_):
"""See http://www.codinghorror.com/blog/archives/001018.html"""
return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)]
@ -247,9 +248,6 @@ def getTitle(media_dict):
except:
log.error('Could not get title for %s', getIdentifier(media_dict))
return None
log.error('Could not get title for %s', getIdentifier(media_dict))
return None
except:
log.error('Could not get title for library item: %s', media_dict)
return None
@ -296,8 +294,11 @@ def isSubFolder(sub_folder, base_folder):
# Returns True if sub_folder is the same as or inside base_folder
return base_folder and sub_folder and ss(os.path.normpath(base_folder).rstrip(os.path.sep) + os.path.sep) in ss(os.path.normpath(sub_folder).rstrip(os.path.sep) + os.path.sep)
# From SABNZBD
re_password = [re.compile(r'(.+){{([^{}]+)}}$'), re.compile(r'(.+)\s+password\s*=\s*(.+)$', re.I)]
def scanForPassword(name):
m = None
for reg in re_password:

13
couchpotato/core/loader.py

@ -12,19 +12,22 @@ log = CPLog(__name__)
class Loader(object):
plugins = {}
providers = {}
modules = {}
def __init__(self):
self.plugins = {}
self.providers = {}
self.modules = {}
self.paths = {}
def preload(self, root = ''):
core = os.path.join(root, 'couchpotato', 'core')
self.paths = {
self.paths.update({
'core': (0, 'couchpotato.core._base', os.path.join(core, '_base')),
'plugin': (1, 'couchpotato.core.plugins', os.path.join(core, 'plugins')),
'notifications': (20, 'couchpotato.core.notifications', os.path.join(core, 'notifications')),
'downloaders': (20, 'couchpotato.core.downloaders', os.path.join(core, 'downloaders')),
}
})
# Add media to loader
self.addPath(root, ['couchpotato', 'core', 'media'], 25, recursive = True)

1
couchpotato/core/media/__init__.py

@ -5,6 +5,7 @@ from couchpotato import get_db, CPLog
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.plugins.base import Plugin
import six
log = CPLog(__name__)

1
couchpotato/core/media/_base/library/__init__.py

@ -1,5 +1,6 @@
from .main import Library
def autoload():
return Library()

1
couchpotato/core/media/_base/matcher/__init__.py

@ -1,5 +1,6 @@
from .main import Matcher
def autoload():
return Matcher()

10
couchpotato/core/media/_base/media/index.py

@ -99,7 +99,7 @@ from couchpotato.core.helpers.encoding import simplifyString"""
class TitleIndex(TreeBasedIndex):
_version = 1
_version = 2
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
from string import ascii_letters
@ -113,14 +113,14 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
return self.simplify(key)
def make_key_value(self, data):
if data.get('_t') == 'media' and data.get('title') is not None:
if data.get('_t') == 'media' and data.get('title') is not None and len(data.get('title')) > 0:
return self.simplify(data['title']), None
def simplify(self, title):
title = toUnicode(title)
nr_prefix = '' if title[0] in ascii_letters else '#'
nr_prefix = '' if title and len(title) > 0 and title[0] in ascii_letters else '#'
title = simplifyString(title)
for prefix in ['the ']:
@ -132,7 +132,7 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
class StartsWithIndex(TreeBasedIndex):
_version = 1
_version = 2
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
from string import ascii_letters
@ -158,4 +158,4 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
title = title[len(prefix):]
break
return str(title[0] if title[0] in ascii_letters else '#').lower()
return str(title[0] if title and len(title) > 0 and title[0] in ascii_letters else '#').lower()

18
couchpotato/core/media/_base/media/main.py

@ -132,10 +132,10 @@ class MediaPlugin(MediaBase):
if media:
# Attach category
if media.get('category_id'):
media['category'] = db.get('id', media.get('category_id'))
try: media['category'] = db.get('id', media.get('category_id'))
except: pass
media['releases'] = list(fireEvent('release.for_media', media['_id'], single = True))
media['releases'] = fireEvent('release.for_media', media['_id'], single = True)
return media
@ -222,7 +222,6 @@ class MediaPlugin(MediaBase):
del filter_by['media_status']
del filter_by['release_status']
# Filter by combining ids
for x in filter_by:
media_ids = [n for n in media_ids if n in filter_by[x]]
@ -281,7 +280,7 @@ class MediaPlugin(MediaBase):
for media_type in fireEvent('media.types', merge = True):
def tempList(*args, **kwargs):
return self.listView(types = media_type, *args, **kwargs)
return self.listView(types = media_type, **kwargs)
addApiView('%s.list' % media_type, tempList)
def availableChars(self, types = None, status = None, release_status = None):
@ -350,7 +349,7 @@ class MediaPlugin(MediaBase):
for media_type in fireEvent('media.types', merge = True):
def tempChar(*args, **kwargs):
return self.charView(types = media_type, *args, **kwargs)
return self.charView(types = media_type, **kwargs)
addApiView('%s.available_chars' % media_type, tempChar)
def delete(self, media_id, delete_from = None):
@ -366,7 +365,7 @@ class MediaPlugin(MediaBase):
deleted = True
else:
media_releases = list(fireEvent('release.for_media', media['_id'], single = True))
media_releases = fireEvent('release.for_media', media['_id'], single = True)
total_releases = len(media_releases)
total_deleted = 0
@ -382,9 +381,8 @@ class MediaPlugin(MediaBase):
if release.get('status') == 'done':
db.delete(release)
total_deleted += 1
new_media_status = 'active'
if total_releases == total_deleted:
if (total_releases == total_deleted and media['status'] != 'active') or (delete_from == 'wanted' and media['status'] == 'active'):
db.delete(media)
deleted = True
elif new_media_status:
@ -432,7 +430,7 @@ class MediaPlugin(MediaBase):
move_to_wanted = True
profile = db.get('id', m['profile_id'])
media_releases = list(fireEvent('release.for_media', m['_id'], single = True))
media_releases = fireEvent('release.for_media', m['_id'], single = True)
for q_identifier in profile['qualities']:
index = profile['qualities'].index(q_identifier)

10
couchpotato/core/media/_base/providers/base.py

@ -244,10 +244,16 @@ class YarrProvider(Provider):
return 0
def getCatId(self, identifier):
def getCatId(self, quality = None):
if not quality: quality = {}
identifier = quality.get('identifier')
want_3d = False
if quality.get('custom'):
want_3d = quality['custom'].get('3d')
for ids, qualities in self.cat_ids:
if identifier in qualities:
if identifier in qualities or (want_3d and '3d' in qualities):
return ids
if self.cat_backup_id:

8
couchpotato/core/media/_base/providers/nzb/binsearch.py

@ -18,7 +18,7 @@ class Base(NZBProvider):
'search': 'https://www.binsearch.info/index.php?%s',
}
http_time_between_calls = 4 # Seconds
http_time_between_calls = 4 # Seconds
def _search(self, media, quality, results):
@ -28,7 +28,7 @@ class Base(NZBProvider):
try:
html = BeautifulSoup(data)
main_table = html.find('table', attrs = {'id':'r2'})
main_table = html.find('table', attrs = {'id': 'r2'})
if not main_table:
return
@ -36,11 +36,11 @@ class Base(NZBProvider):
items = main_table.find_all('tr')
for row in items:
title = row.find('span', attrs = {'class':'s'})
title = row.find('span', attrs = {'class': 's'})
if not title: continue
nzb_id = row.find('input', attrs = {'type':'checkbox'})['name']
nzb_id = row.find('input', attrs = {'type': 'checkbox'})['name']
info = row.find('span', attrs = {'class':'d'})
size_match = re.search('size:.(?P<size>[0-9\.]+.[GMB]+)', info.text)

4
couchpotato/core/media/_base/providers/nzb/nzbclub.py

@ -18,7 +18,7 @@ class Base(NZBProvider, RSS):
'search': 'https://www.nzbclub.com/nzbfeeds.aspx?%s',
}
http_time_between_calls = 4 #seconds
http_time_between_calls = 4 # seconds
def _search(self, media, quality, results):
@ -55,7 +55,7 @@ class Base(NZBProvider, RSS):
def getMoreInfo(self, item):
full_description = self.getCache('nzbclub.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
html = BeautifulSoup(full_description)
nfo_pre = html.find('pre', attrs = {'class':'nfo'})
nfo_pre = html.find('pre', attrs = {'class': 'nfo'})
description = toUnicode(nfo_pre.text) if nfo_pre else ''
item['description'] = description

2
couchpotato/core/media/_base/providers/nzb/nzbindex.py

@ -91,7 +91,7 @@ class Base(NZBProvider, RSS):
nfo_url = re.search('href=\"(?P<nfo>.+)\" ', item['description']).group('nfo')
full_description = self.getCache('nzbindex.%s' % item['id'], url = nfo_url, cache_timeout = 25920000)
html = BeautifulSoup(full_description)
item['description'] = toUnicode(html.find('pre', attrs = {'id':'nfo0'}).text)
item['description'] = toUnicode(html.find('pre', attrs = {'id': 'nfo0'}).text)
except:
pass

4
couchpotato/core/media/_base/providers/nzb/omgwtfnzbs.py

@ -20,7 +20,7 @@ class Base(NZBProvider, RSS):
'detail_url': 'https://omgwtfnzbs.org/details.php?id=%s',
}
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
cat_ids = [
([15], ['dvdrip']),
@ -42,7 +42,7 @@ class Base(NZBProvider, RSS):
q = '%s %s' % (title, movie['info']['year'])
params = tryUrlencode({
'search': q,
'catid': ','.join([str(x) for x in self.getCatId(quality['identifier'])]),
'catid': ','.join([str(x) for x in self.getCatId(quality)]),
'user': self.conf('username', default = ''),
'api': self.conf('api_key', default = ''),
})

6
couchpotato/core/media/_base/providers/torrent/bithdtv.py

@ -21,7 +21,7 @@ class Base(TorrentProvider):
}
# Searches for movies only - BiT-HDTV's subcategory and resolution search filters appear to be broken
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
def _search(self, media, quality, results):
@ -40,7 +40,7 @@ class Base(TorrentProvider):
html = BeautifulSoup(data)
try:
result_table = html.find('table', attrs = {'width' : '750', 'class' : ''})
result_table = html.find('table', attrs = {'width': '750', 'class': ''})
if result_table is None:
return
@ -74,7 +74,7 @@ class Base(TorrentProvider):
def getMoreInfo(self, item):
full_description = self.getCache('bithdtv.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
html = BeautifulSoup(full_description)
nfo_pre = html.find('table', attrs = {'class':'detail'})
nfo_pre = html.find('table', attrs = {'class': 'detail'})
description = toUnicode(nfo_pre.text) if nfo_pre else ''
item['description'] = description

6
couchpotato/core/media/_base/providers/torrent/bitsoup.py

@ -20,7 +20,7 @@ class Base(TorrentProvider):
'baseurl': 'https://www.bitsoup.me/%s',
}
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
def _searchOnTitle(self, title, movie, quality, results):
@ -30,7 +30,7 @@ class Base(TorrentProvider):
})
url = "%s&%s" % (self.urls['search'], arguments)
url = self.urls['search'] % self.buildUrl(media, quality)
url = self.urls['search'] % self.buildUrl(movie, quality)
data = self.getHTMLData(url)
if data:
@ -74,7 +74,6 @@ class Base(TorrentProvider):
except:
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
def getLoginParams(self):
return {
'username': self.conf('username'),
@ -82,7 +81,6 @@ class Base(TorrentProvider):
'ssl': 'yes',
}
def loginSuccess(self, output):
return 'logout.php' in output.lower()

4
couchpotato/core/media/_base/providers/torrent/hdbits.py

@ -19,7 +19,7 @@ class Base(TorrentProvider):
'api': 'https://hdbits.org/api/torrents'
}
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
def _post_query(self, **params):
@ -56,7 +56,7 @@ class Base(TorrentProvider):
'name': result['name'],
'url': self.urls['download'] % (result['id'], self.conf('passkey')),
'detail_url': self.urls['detail'] % result['id'],
'size': tryInt(result['size'])/1024/1024,
'size': tryInt(result['size']) / 1024 / 1024,
'seeders': tryInt(result['seeders']),
'leechers': tryInt(result['leechers'])
})

12
couchpotato/core/media/_base/providers/torrent/ilovetorrents.py

@ -23,9 +23,9 @@ class Base(TorrentProvider):
}
cat_ids = [
(['41'], ['720p', '1080p', 'brrip']),
(['19'], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
(['20'], ['dvdr'])
(['41'], ['720p', '1080p', 'brrip']),
(['19'], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
(['20'], ['dvdr'])
]
cat_backup_id = 200
@ -36,7 +36,7 @@ class Base(TorrentProvider):
page = 0
total_pages = 1
cats = self.getCatId(quality['identifier'])
cats = self.getCatId(quality)
while page < total_pages:
@ -79,7 +79,7 @@ class Base(TorrentProvider):
return confirmed + trusted + vip + moderated
id = re.search('id=(?P<id>\d+)&', link).group('id')
url = self.urls['download'] % (download)
url = self.urls['download'] % download
fileSize = self.parseSize(result.select('td.rowhead')[5].text)
results.append({
@ -113,7 +113,7 @@ class Base(TorrentProvider):
try:
full_description = self.getHTMLData(item['detail_url'])
html = BeautifulSoup(full_description)
nfo_pre = html.find('td', attrs = {'class':'main'}).findAll('table')[1]
nfo_pre = html.find('td', attrs = {'class': 'main'}).findAll('table')[1]
description = toUnicode(nfo_pre.text) if nfo_pre else ''
except:
log.error('Failed getting more info for %s', item['name'])

20
couchpotato/core/media/_base/providers/torrent/iptorrents.py

@ -18,21 +18,21 @@ class Base(TorrentProvider):
'base_url': 'https://www.iptorrents.com',
'login': 'https://www.iptorrents.com/torrents/',
'login_check': 'https://www.iptorrents.com/inbox.php',
'search': 'https://www.iptorrents.com/torrents/?l%d=1%s&q=%s&qf=ti&p=%d',
'search': 'https://www.iptorrents.com/torrents/?%s%%s&q=%s&qf=ti&p=%%d',
}
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
cat_backup_id = None
def buildUrl(self, title, media, quality):
return self._buildUrl(title.replace(':', ''), quality['identifier'])
return self._buildUrl(title.replace(':', ''), quality)
def _buildUrl(self, query, quality_identifier):
def _buildUrl(self, query, quality):
cat_ids = self.getCatId(quality_identifier)
cat_ids = self.getCatId(quality)
if not cat_ids:
log.warning('Unable to find category ids for identifier "%s"', quality_identifier)
log.warning('Unable to find category ids for identifier "%s"', quality.get('identifier'))
return None
return self.urls['search'] % ("&".join(("l%d=" % x) for x in cat_ids), tryUrlencode(query).replace('%', '%%'))
@ -53,14 +53,14 @@ class Base(TorrentProvider):
html = BeautifulSoup(data)
try:
page_nav = html.find('span', attrs = {'class' : 'page_nav'})
page_nav = html.find('span', attrs = {'class': 'page_nav'})
if page_nav:
next_link = page_nav.find("a", text = "Next")
if next_link:
final_page_link = next_link.previous_sibling.previous_sibling
pages = int(final_page_link.string)
result_table = html.find('table', attrs = {'class' : 'torrents'})
result_table = html.find('table', attrs = {'class': 'torrents'})
if not result_table or 'nothing found!' in data.lower():
return
@ -80,8 +80,8 @@ class Base(TorrentProvider):
torrent_download_url = self.urls['base_url'] + (result.find_all('td')[3].find('a'))['href'].replace(' ', '.')
torrent_details_url = self.urls['base_url'] + torrent['href']
torrent_size = self.parseSize(result.find_all('td')[5].string)
torrent_seeders = tryInt(result.find('td', attrs = {'class' : 'ac t_seeders'}).string)
torrent_leechers = tryInt(result.find('td', attrs = {'class' : 'ac t_leechers'}).string)
torrent_seeders = tryInt(result.find('td', attrs = {'class': 'ac t_seeders'}).string)
torrent_leechers = tryInt(result.find('td', attrs = {'class': 'ac t_leechers'}).string)
results.append({
'id': torrent_id,

4
couchpotato/core/media/_base/providers/torrent/kickasstorrents.py

@ -26,7 +26,7 @@ class Base(TorrentMagnetProvider):
(['dvd'], ['dvdr']),
]
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
cat_backup_id = None
proxy_list = [
@ -42,7 +42,7 @@ class Base(TorrentMagnetProvider):
if data:
cat_ids = self.getCatId(quality['identifier'])
cat_ids = self.getCatId(quality)
table_order = ['name', 'size', None, 'age', 'seeds', 'leechers']
try:

36
couchpotato/core/media/_base/providers/torrent/passthepopcorn.py

@ -18,12 +18,12 @@ log = CPLog(__name__)
class Base(TorrentProvider):
urls = {
'domain': 'https://tls.passthepopcorn.me',
'detail': 'https://tls.passthepopcorn.me/torrents.php?torrentid=%s',
'torrent': 'https://tls.passthepopcorn.me/torrents.php',
'login': 'https://tls.passthepopcorn.me/ajax.php?action=login',
'login_check': 'https://tls.passthepopcorn.me/ajax.php?action=login',
'search': 'https://tls.passthepopcorn.me/search/%s/0/7/%d'
'domain': 'https://tls.passthepopcorn.me',
'detail': 'https://tls.passthepopcorn.me/torrents.php?torrentid=%s',
'torrent': 'https://tls.passthepopcorn.me/torrents.php',
'login': 'https://tls.passthepopcorn.me/ajax.php?action=login',
'login_check': 'https://tls.passthepopcorn.me/ajax.php?action=login',
'search': 'https://tls.passthepopcorn.me/search/%s/0/7/%d'
}
http_time_between_calls = 2
@ -136,23 +136,23 @@ class Base(TorrentProvider):
def htmlToUnicode(self, text):
def fixup(m):
text = m.group(0)
if text[:2] == "&#":
txt = m.group(0)
if txt[:2] == "&#":
# character reference
try:
if text[:3] == "&#x":
return unichr(int(text[3:-1], 16))
if txt[:3] == "&#x":
return unichr(int(txt[3:-1], 16))
else:
return unichr(int(text[2:-1]))
return unichr(int(txt[2:-1]))
except ValueError:
pass
else:
# named entity
try:
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
txt = unichr(htmlentitydefs.name2codepoint[txt[1:-1]])
except KeyError:
pass
return text # leave as is
return txt # leave as is
return re.sub("&#?\w+;", fixup, six.u('%s') % text)
def unicodeToASCII(self, text):
@ -164,11 +164,11 @@ class Base(TorrentProvider):
def getLoginParams(self):
return {
'username': self.conf('username'),
'password': self.conf('password'),
'passkey': self.conf('passkey'),
'keeplogged': '1',
'login': 'Login'
'username': self.conf('username'),
'password': self.conf('password'),
'passkey': self.conf('passkey'),
'keeplogged': '1',
'login': 'Login'
}
def loginSuccess(self, output):

2
couchpotato/core/media/_base/providers/torrent/publichd.py

@ -34,7 +34,7 @@ class Base(TorrentMagnetProvider):
query = self.buildUrl(media)
params = tryUrlencode({
'page':'torrents',
'page': 'torrents',
'search': query,
'active': 1,
})

18
couchpotato/core/media/_base/providers/torrent/sceneaccess.py

@ -22,7 +22,7 @@ class Base(TorrentProvider):
'download': 'https://www.sceneaccess.eu/%s',
}
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
def _search(self, media, quality, results):
@ -33,16 +33,16 @@ class Base(TorrentProvider):
html = BeautifulSoup(data)
try:
resultsTable = html.find('table', attrs = {'id' : 'torrents-table'})
resultsTable = html.find('table', attrs = {'id': 'torrents-table'})
if resultsTable is None:
return
entries = resultsTable.find_all('tr', attrs = {'class' : 'tt_row'})
entries = resultsTable.find_all('tr', attrs = {'class': 'tt_row'})
for result in entries:
link = result.find('td', attrs = {'class' : 'ttr_name'}).find('a')
url = result.find('td', attrs = {'class' : 'td_dl'}).find('a')
leechers = result.find('td', attrs = {'class' : 'ttr_leechers'}).find('a')
link = result.find('td', attrs = {'class': 'ttr_name'}).find('a')
url = result.find('td', attrs = {'class': 'td_dl'}).find('a')
leechers = result.find('td', attrs = {'class': 'ttr_leechers'}).find('a')
torrent_id = link['href'].replace('details?id=', '')
results.append({
@ -50,8 +50,8 @@ class Base(TorrentProvider):
'name': link['title'],
'url': self.urls['download'] % url['href'],
'detail_url': self.urls['detail'] % torrent_id,
'size': self.parseSize(result.find('td', attrs = {'class' : 'ttr_size'}).contents[0]),
'seeders': tryInt(result.find('td', attrs = {'class' : 'ttr_seeders'}).find('a').string),
'size': self.parseSize(result.find('td', attrs = {'class': 'ttr_size'}).contents[0]),
'seeders': tryInt(result.find('td', attrs = {'class': 'ttr_seeders'}).find('a').string),
'leechers': tryInt(leechers.string) if leechers else 0,
'get_more_info': self.getMoreInfo,
})
@ -62,7 +62,7 @@ class Base(TorrentProvider):
def getMoreInfo(self, item):
full_description = self.getCache('sceneaccess.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
html = BeautifulSoup(full_description)
nfo_pre = html.find('div', attrs = {'id':'details_table'})
nfo_pre = html.find('div', attrs = {'id': 'details_table'})
description = toUnicode(nfo_pre.text) if nfo_pre else ''
item['description'] = description

4
couchpotato/core/media/_base/providers/torrent/thepiratebay.py

@ -39,7 +39,7 @@ class Base(TorrentMagnetProvider):
page = 0
total_pages = 1
cats = self.getCatId(quality['identifier'])
cats = self.getCatId(quality)
base_search_url = self.urls['search'] % self.getDomain()
@ -108,7 +108,7 @@ class Base(TorrentMagnetProvider):
def getMoreInfo(self, item):
full_description = self.getCache('tpb.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
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 ''
item['description'] = description

8
couchpotato/core/media/_base/providers/torrent/torrentbytes.py

@ -31,19 +31,19 @@ class Base(TorrentProvider):
([20], ['dvdr']),
]
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
cat_backup_id = None
def _searchOnTitle(self, title, movie, quality, results):
url = self.urls['search'] % (tryUrlencode('%s %s' % (title.replace(':', ''), movie['info']['year'])), self.getCatId(quality['identifier'])[0])
url = self.urls['search'] % (tryUrlencode('%s %s' % (title.replace(':', ''), movie['info']['year'])), self.getCatId(quality)[0])
data = self.getHTMLData(url)
if data:
html = BeautifulSoup(data)
try:
result_table = html.find('table', attrs = {'border' : '1'})
result_table = html.find('table', attrs = {'border': '1'})
if not result_table:
return
@ -52,7 +52,7 @@ class Base(TorrentProvider):
for result in entries[1:]:
cells = result.find_all('td')
link = cells[1].find('a', attrs = {'class' : 'index'})
link = cells[1].find('a', attrs = {'class': 'index'})
full_id = link['href'].replace('details.php?id=', '')
torrent_id = full_id[:6]

2
couchpotato/core/media/_base/providers/torrent/torrentday.py

@ -16,7 +16,7 @@ class Base(TorrentProvider):
'download': 'http://www.td.af/download.php/%s/%s',
}
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
def _search(self, media, quality, results):

14
couchpotato/core/media/_base/providers/torrent/torrentleech.py

@ -21,7 +21,7 @@ class Base(TorrentProvider):
'download': 'http://www.torrentleech.org%s',
}
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
cat_backup_id = None
def _search(self, media, quality, results):
@ -34,7 +34,7 @@ class Base(TorrentProvider):
html = BeautifulSoup(data)
try:
result_table = html.find('table', attrs = {'id' : 'torrenttable'})
result_table = html.find('table', attrs = {'id': 'torrenttable'})
if not result_table:
return
@ -42,9 +42,9 @@ class Base(TorrentProvider):
for result in entries[1:]:
link = result.find('td', attrs = {'class' : 'name'}).find('a')
url = result.find('td', attrs = {'class' : 'quickdownload'}).find('a')
details = result.find('td', attrs = {'class' : 'name'}).find('a')
link = result.find('td', attrs = {'class': 'name'}).find('a')
url = result.find('td', attrs = {'class': 'quickdownload'}).find('a')
details = result.find('td', attrs = {'class': 'name'}).find('a')
results.append({
'id': link['href'].replace('/torrent/', ''),
@ -52,8 +52,8 @@ class Base(TorrentProvider):
'url': self.urls['download'] % url['href'],
'detail_url': self.urls['download'] % details['href'],
'size': self.parseSize(result.find_all('td')[4].string),
'seeders': tryInt(result.find('td', attrs = {'class' : 'seeders'}).string),
'leechers': tryInt(result.find('td', attrs = {'class' : 'leechers'}).string),
'seeders': tryInt(result.find('td', attrs = {'class': 'seeders'}).string),
'leechers': tryInt(result.find('td', attrs = {'class': 'leechers'}).string),
})
except:

10
couchpotato/core/media/_base/providers/torrent/torrentshack.py

@ -21,7 +21,7 @@ class Base(TorrentProvider):
'download': 'https://torrentshack.net/%s',
}
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # Seconds
def _search(self, media, quality, results):
@ -32,16 +32,16 @@ class Base(TorrentProvider):
html = BeautifulSoup(data)
try:
result_table = html.find('table', attrs = {'id' : 'torrent_table'})
result_table = html.find('table', attrs = {'id': 'torrent_table'})
if not result_table:
return
entries = result_table.find_all('tr', attrs = {'class' : 'torrent'})
entries = result_table.find_all('tr', attrs = {'class': 'torrent'})
for result in entries:
link = result.find('span', attrs = {'class' : 'torrent_name_link'}).parent
url = result.find('td', attrs = {'class' : 'torrent_td'}).find('a')
link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent
url = result.find('td', attrs = {'class': 'torrent_td'}).find('a')
results.append({
'id': link['href'].replace('torrents.php?torrentid=', ''),

128
couchpotato/core/media/_base/providers/torrent/torrentz.py

@ -0,0 +1,128 @@
import re
import traceback
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.rss import RSS
from couchpotato.core.helpers.variable import tryInt, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.torrent.base import TorrentMagnetProvider
import six
log = CPLog(__name__)
class Base(TorrentMagnetProvider, RSS):
urls = {
'detail': 'https://torrentz.eu/%s',
'search': 'https://torrentz.eu/feed?q=%s',
'verified_search': 'https://torrentz.eu/feed_verified?q=%s'
}
http_time_between_calls = 0
def _search(self, media, quality, results):
search_url = self.urls['verified_search'] if self.conf('verified_only') else self.urls['search']
# Create search parameters
search_params = self.buildUrl(media)
smin = quality.get('size_min')
smax = quality.get('size_max')
if smin and smax:
search_params += ' size %sm - %sm' % (smin, smax)
min_seeds = tryInt(self.conf('minimal_seeds'))
if min_seeds:
search_params += ' seed > %s' % (min_seeds - 1)
rss_data = self.getRSSData(search_url % search_params)
if rss_data:
try:
for result in rss_data:
name = self.getTextElement(result, 'title')
detail_url = self.getTextElement(result, 'link')
description = self.getTextElement(result, 'description')
magnet = splitString(detail_url, '/')[-1]
magnet_url = 'magnet:?xt=urn:btih:%s&dn=%s&tr=%s' % (magnet.upper(), tryUrlencode(name), tryUrlencode('udp://tracker.openbittorrent.com/announce'))
reg = re.search('Size: (?P<size>\d+) MB Seeds: (?P<seeds>[\d,]+) Peers: (?P<peers>[\d,]+)', six.text_type(description))
size = reg.group('size')
seeds = reg.group('seeds').replace(',', '')
peers = reg.group('peers').replace(',', '')
results.append({
'id': magnet,
'name': six.text_type(name),
'url': magnet_url,
'detail_url': detail_url,
'size': tryInt(size),
'seeders': tryInt(seeds),
'leechers': tryInt(peers),
})
except:
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
config = [{
'name': 'torrentz',
'groups': [
{
'tab': 'searcher',
'list': 'torrent_providers',
'name': 'Torrentz',
'description': 'Torrentz is a free, fast and powerful meta-search engine. <a href="https://torrentz.eu/">Torrentz</a>',
'wizard': True,
'options': [
{
'name': 'enabled',
'type': 'enabler',
'default': False
},
{
'name': 'verified_only',
'type': 'bool',
'default': True,
'advanced': True,
'description': 'Only search verified releases',
},
{
'name': 'minimal_seeds',
'type': 'int',
'default': 1,
'advanced': True,
'description': 'Only return releases with minimal X seeds',
},
{
'name': 'seed_ratio',
'label': 'Seed ratio',
'type': 'float',
'default': 1,
'description': 'Will not be (re)moved until this seed ratio is met.',
},
{
'name': 'seed_time',
'label': 'Seed time',
'type': 'int',
'default': 40,
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
},
{
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
'type': 'int',
'default': 0,
'description': 'Starting score for each release found via this provider.',
}
],
}
]
}]

4
couchpotato/core/media/_base/providers/torrent/yify.py

@ -16,7 +16,7 @@ class Base(TorrentProvider):
'detail': '%s/api/movie.json?id=%s'
}
http_time_between_calls = 1 #seconds
http_time_between_calls = 1 # seconds
proxy_list = [
'http://yify.unlocktorrent.com',
@ -81,7 +81,7 @@ config = [{
{
'name': 'enabled',
'type': 'enabler',
'default': 0
'default': False
},
{
'name': 'domain',

46
couchpotato/core/media/_base/search/static/search.css

@ -6,12 +6,11 @@
top: 0;
text-align: right;
height: 100%;
border-bottom: 4px solid transparent;
transition: all .4s cubic-bezier(0.9,0,0.1,1);
position: absolute;
z-index: 20;
border: 1px solid transparent;
border-width: 0 0 4px;
border: 0 solid transparent;
border-bottom-width: 4px;
}
.search_form:hover {
border-color: #047792;
@ -22,19 +21,19 @@
right: 44px;
}
}
.search_form.focused,
.search_form.shown {
border-color: #04bce6;
}
.search_form .input {
height: 100%;
overflow: hidden;
width: 45px;
transition: all .4s cubic-bezier(0.9,0,0.1,1);
}
.search_form.focused .input,
.search_form.shown .input {
width: 380px;
@ -49,7 +48,6 @@
color: #FFF;
font-size: 25px;
height: 100%;
padding: 10px;
width: 100%;
opacity: 0;
padding: 0 40px 0 10px;
@ -59,23 +57,23 @@
.search_form.shown .input input {
opacity: 1;
}
.search_form input::-ms-clear {
width : 0;
height: 0;
}
@media all and (max-width: 480px) {
.search_form .input input {
font-size: 15px;
}
.search_form.focused .input,
.search_form.shown .input {
width: 277px;
}
}
.search_form .input a {
position: absolute;
top: 0;
@ -89,7 +87,7 @@
font-size: 15px;
color: #FFF;
}
.search_form .input a:after {
content: "\e03e";
}
@ -97,7 +95,7 @@
.search_form.shown.filled .input a:after {
content: "\e04e";
}
@media all and (max-width: 480px) {
.search_form .input a {
line-height: 44px;
@ -167,13 +165,13 @@
.media_result .options select[name=title] { width: 170px; }
.media_result .options select[name=profile] { width: 90px; }
.media_result .options select[name=category] { width: 80px; }
@media all and (max-width: 480px) {
.media_result .options select[name=title] { width: 90px; }
.media_result .options select[name=profile] { width: 50px; }
.media_result .options select[name=category] { width: 50px; }
}
.media_result .options .button {
@ -227,14 +225,14 @@
right: 7px;
vertical-align: middle;
}
.media_result .info h2 {
margin: 0;
font-weight: normal;
font-size: 20px;
padding: 0;
}
.search_form .info h2 {
position: absolute;
width: 100%;
@ -247,12 +245,12 @@
overflow: hidden;
white-space: nowrap;
}
.search_form .info h2 .title {
position: absolute;
width: 88%;
}
.media_result .info h2 .year {
padding: 0 5px;
text-align: center;
@ -260,14 +258,14 @@
width: 12%;
right: 0;
}
@media all and (max-width: 480px) {
.search_form .info h2 .year {
font-size: 12px;
margin-top: 7px;
}
}
.search_form .mask,
@ -277,4 +275,4 @@
width: 100%;
left: 0;
top: 0;
}
}

46
couchpotato/core/media/_base/search/static/search.js

@ -16,7 +16,7 @@ Block.Search = new Class({
'keyup': self.keyup.bind(self),
'focus': function(){
if(focus_timer) clearTimeout(focus_timer);
self.el.addClass('focused')
self.el.addClass('focused');
if(this.get('value'))
self.hideResults(false)
},
@ -57,17 +57,17 @@ Block.Search = new Class({
(e).preventDefault();
if(self.last_q === ''){
self.input.blur()
self.input.blur();
self.last_q = null;
}
else {
self.last_q = '';
self.input.set('value', '');
self.input.focus()
self.input.focus();
self.media = {}
self.results.empty()
self.media = {};
self.results.empty();
self.el.removeClass('filled')
}
@ -92,16 +92,16 @@ Block.Search = new Class({
self.hidden = bool;
},
keyup: function(e){
keyup: function(){
var self = this;
self.el[self.q() ? 'addClass' : 'removeClass']('filled')
self.el[self.q() ? 'addClass' : 'removeClass']('filled');
if(self.q() != self.last_q){
if(self.api_request && self.api_request.isRunning())
self.api_request.cancel();
if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer)
if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer);
self.autocomplete_timer = self.autocomplete.delay(300, self)
}
@ -111,7 +111,7 @@ Block.Search = new Class({
var self = this;
if(!self.q()){
self.hideResults(true)
self.hideResults(true);
return
}
@ -139,7 +139,7 @@ Block.Search = new Class({
})
}
else
self.fill(q, cache)
self.fill(q, cache);
self.last_q = q;
@ -148,31 +148,31 @@ Block.Search = new Class({
fill: function(q, json){
var self = this;
self.cache[q] = json
self.cache[q] = json;
self.media = {}
self.results.empty()
Object.each(json, function(media, type){
self.media = {};
self.results.empty();
Object.each(json, function(media){
if(typeOf(media) == 'array'){
Object.each(media, function(m){
var m = new Block.Search[m.type.capitalize() + 'Item'](m);
$(m).inject(self.results)
self.media[m.imdb || 'r-'+Math.floor(Math.random()*10000)] = m
$(m).inject(self.results);
self.media[m.imdb || 'r-'+Math.floor(Math.random()*10000)] = m;
if(q == m.imdb)
m.showOptions()
});
}
})
});
// Calculate result heights
var w = window.getSize(),
rc = self.result_container.getCoordinates();
self.results.setStyle('max-height', (w.y - rc.top - 50) + 'px')
self.results.setStyle('max-height', (w.y - rc.top - 50) + 'px');
self.mask.fade('out')
},
@ -185,4 +185,4 @@ Block.Search = new Class({
return this.input.get('value').trim();
}
});
});

15
couchpotato/core/media/_base/searcher/main.py

@ -14,9 +14,11 @@ log = CPLog(__name__)
class Searcher(SearcherBase):
# noinspection PyMissingConstructor
def __init__(self):
addEvent('searcher.protocols', self.getSearchProtocols)
addEvent('searcher.contains_other_quality', self.containsOtherQuality)
addEvent('searcher.correct_3d', self.correct3D)
addEvent('searcher.correct_year', self.correctYear)
addEvent('searcher.correct_name', self.correctName)
addEvent('searcher.correct_words', self.correctWords)
@ -123,6 +125,17 @@ class Searcher(SearcherBase):
return not (found.get(preferred_quality['identifier']) and len(found) == 1)
def correct3D(self, nzb, preferred_quality = None):
if not preferred_quality: preferred_quality = {}
if not preferred_quality.get('custom'): return
threed = preferred_quality['custom'].get('3d')
# Try guessing via quality tags
guess = fireEvent('quality.guess', [nzb.get('name')], single = True)
return threed == guess.get('is_3d')
def correctYear(self, haystack, year, year_range):
if not isinstance(haystack, (list, tuple, set)):
@ -183,7 +196,7 @@ class Searcher(SearcherBase):
req = splitString(req_set, '&')
req_match += len(list(set(rel_words) & set(req))) == len(req)
if len(required_words) > 0 and req_match == 0:
if len(required_words) > 0 and req_match == 0:
log.info2('Wrong: Required word missing: %s', rel_name)
return False

60
couchpotato/core/media/movie/_base/static/list.js

@ -26,7 +26,7 @@ var MovieList = new Class({
self.filter = self.options.filter || {
'starts_with': null,
'search': null
}
};
self.el = new Element('div.movies').adopt(
self.title = self.options.title ? new Element('h2', {
@ -52,7 +52,7 @@ var MovieList = new Class({
self.getMovies();
App.on('movie.added', self.movieAdded.bind(self))
App.on('movie.added', self.movieAdded.bind(self));
App.on('movie.deleted', self.movieDeleted.bind(self))
},
@ -96,7 +96,7 @@ var MovieList = new Class({
if(self.options.load_more)
self.scrollspy = new ScrollSpy({
min: function(){
var c = self.load_more.getCoordinates()
var c = self.load_more.getCoordinates();
return c.top - window.document.getSize().y - 300
},
onEnter: self.loadMore.bind(self)
@ -179,7 +179,7 @@ var MovieList = new Class({
m.fireEvent('injected');
self.movies.include(m)
self.movies.include(m);
self.movies_added[movie._id] = true;
},
@ -187,7 +187,7 @@ var MovieList = new Class({
var self = this;
var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
self.el.addClass('with_navigation')
self.el.addClass('with_navigation');
self.navigation = new Element('div.alph_nav').adopt(
self.mass_edit_form = new Element('div.mass_edit_form').adopt(
@ -242,7 +242,7 @@ var MovieList = new Class({
this.addClass(a);
el.inject(el.getParent(), 'top');
el.getSiblings().hide()
el.getSiblings().hide();
setTimeout(function(){
el.getSiblings().setStyle('display', null);
}, 100)
@ -286,7 +286,7 @@ var MovieList = new Class({
'status': self.options.status
}, self.filter),
'onSuccess': function(json){
available_chars = json.chars
available_chars = json.chars;
available_chars.each(function(c){
self.letters[c.capitalize()].addClass('available')
@ -300,7 +300,7 @@ var MovieList = new Class({
self.navigation_alpha = new Element('ul.numbers', {
'events': {
'click:relay(li.available)': function(e, el){
self.activateLetter(el.get('data-letter'))
self.activateLetter(el.get('data-letter'));
self.getMovies(true)
}
}
@ -318,7 +318,7 @@ var MovieList = new Class({
// All
self.letters['all'] = new Element('li.letter_all.available.active', {
'text': 'ALL',
'text': 'ALL'
}).inject(self.navigation_alpha);
// Chars
@ -334,7 +334,7 @@ var MovieList = new Class({
if (self.options.menu.length > 0)
self.options.menu.each(function(menu_item){
self.navigation_menu.addLink(menu_item);
})
});
else
self.navigation_menu.hide();
@ -347,15 +347,15 @@ var MovieList = new Class({
movies = self.movies.length;
self.movies.each(function(movie){
selected += movie.isSelected() ? 1 : 0
})
});
var indeterminate = selected > 0 && selected < movies,
checked = selected == movies && selected > 0;
self.mass_edit_select.set('indeterminate', indeterminate)
self.mass_edit_select.set('indeterminate', indeterminate);
self.mass_edit_select_class[checked ? 'check' : 'uncheck']()
self.mass_edit_select_class.element[indeterminate ? 'addClass' : 'removeClass']('indeterminate')
self.mass_edit_select_class[checked ? 'check' : 'uncheck']();
self.mass_edit_select_class.element[indeterminate ? 'addClass' : 'removeClass']('indeterminate');
self.mass_edit_selected.set('text', selected);
},
@ -371,7 +371,7 @@ var MovieList = new Class({
'events': {
'click': function(e){
(e).preventDefault();
this.set('text', 'Deleting..')
this.set('text', 'Deleting..');
Api.request('media.delete', {
'data': {
'id': ids.join(','),
@ -383,7 +383,7 @@ var MovieList = new Class({
var erase_movies = [];
self.movies.each(function(movie){
if (movie.isSelected()){
$(movie).destroy()
$(movie).destroy();
erase_movies.include(movie);
}
});
@ -410,7 +410,7 @@ var MovieList = new Class({
changeQualitySelected: function(){
var self = this;
var ids = self.getSelectedMovies()
var ids = self.getSelectedMovies();
Api.request('movie.edit', {
'data': {
@ -423,11 +423,11 @@ var MovieList = new Class({
refreshSelected: function(){
var self = this;
var ids = self.getSelectedMovies()
var ids = self.getSelectedMovies();
Api.request('media.refresh', {
'data': {
'id': ids.join(','),
'id': ids.join(',')
}
});
},
@ -435,7 +435,7 @@ var MovieList = new Class({
getSelectedMovies: function(){
var self = this;
var ids = []
var ids = [];
self.movies.each(function(movie){
if (movie.isSelected())
ids.include(movie.get('_id'))
@ -459,11 +459,11 @@ var MovieList = new Class({
reset: function(){
var self = this;
self.movies = []
self.movies = [];
if(self.mass_edit_select)
self.calculateSelected()
self.calculateSelected();
if(self.navigation_alpha)
self.navigation_alpha.getElements('.active').removeClass('active')
self.navigation_alpha.getElements('.active').removeClass('active');
self.offset = 0;
if(self.scrollspy){
@ -475,7 +475,7 @@ var MovieList = new Class({
activateLetter: function(letter){
var self = this;
self.reset()
self.reset();
self.letters[letter || 'all'].addClass('active');
self.filter.starts_with = letter;
@ -487,7 +487,7 @@ var MovieList = new Class({
self.el
.removeClass(self.current_view+'_list')
.addClass(new_view+'_list')
.addClass(new_view+'_list');
self.current_view = new_view;
Cookie.write(self.options.identifier+'_view2', new_view, {duration: 1000});
@ -504,9 +504,9 @@ var MovieList = new Class({
if(self.search_timer) clearTimeout(self.search_timer);
self.search_timer = (function(){
var search_value = self.navigation_search_input.get('value');
if (search_value == self.last_search_value) return
if (search_value == self.last_search_value) return;
self.reset()
self.reset();
self.activateLetter();
self.filter.search = search_value;
@ -563,7 +563,7 @@ var MovieList = new Class({
if(self.loader_first){
var lf = self.loader_first;
self.loader_first.addClass('hide')
self.loader_first.addClass('hide');
self.loader_first = null;
setTimeout(function(){
lf.destroy();
@ -603,10 +603,10 @@ var MovieList = new Class({
var is_empty = self.movies.length == 0 && (self.total_movies == 0 || self.total_movies === undefined);
if(self.title)
self.title[is_empty ? 'hide' : 'show']()
self.title[is_empty ? 'hide' : 'show']();
if(self.description)
self.description.setStyle('display', [is_empty ? 'none' : ''])
self.description.setStyle('display', [is_empty ? 'none' : '']);
if(is_empty && self.options.on_empty_element){
self.options.on_empty_element.inject(self.loader_first || self.title || self.movie_list, 'after');

42
couchpotato/core/media/movie/_base/static/movie.actions.js

@ -60,22 +60,6 @@ var MovieAction = new Class({
'z-index': '1'
}
}).inject(self.movie, 'top').fade('hide');
//self.positionMask();
},
positionMask: function(){
var self = this,
movie = $(self.movie),
s = movie.getSize()
return;
return self.mask.setStyles({
'width': s.x,
'height': s.y
}).position({
'relativeTo': movie
})
},
toElement: function(){
@ -122,7 +106,7 @@ MA.Release = new Class({
});
if(!self.movie.data.releases || self.movie.data.releases.length == 0)
self.el.hide()
self.el.hide();
else
self.showHelper();
@ -164,7 +148,7 @@ MA.Release = new Class({
new Element('span.age', {'text': 'Age'}),
new Element('span.score', {'text': 'Score'}),
new Element('span.provider', {'text': 'Provider'})
).inject(self.release_container)
).inject(self.release_container);
if(self.movie.data.releases)
self.movie.data.releases.each(function(release){
@ -186,13 +170,13 @@ MA.Release = new Class({
}
// Create release
var item = new Element('div', {
release['el'] = new Element('div', {
'class': 'item '+release.status,
'id': 'release_'+release._id
}).adopt(
new Element('span.name', {'text': release_name, 'title': release_name}),
new Element('span.status', {'text': release.status, 'class': 'release_status '+release.status}),
new Element('span.quality', {'text': quality.label || 'n/a'}),
new Element('span.quality', {'text': quality.label + (release.is_3d ? ' 3D' : '') || 'n/a'}),
new Element('span.size', {'text': info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}),
new Element('span.age', {'text': self.get(release, 'age')}),
new Element('span.score', {'text': self.get(release, 'score')}),
@ -219,7 +203,6 @@ MA.Release = new Class({
}
})
).inject(self.release_container);
release['el'] = item;
if(release.status == 'ignored' || release.status == 'failed' || release.status == 'snatched'){
if(!self.last_release || (self.last_release && self.last_release.status != 'snatched' && release.status == 'snatched'))
@ -242,13 +225,13 @@ MA.Release = new Class({
status_el.set('text', new_status);
if(!q && (new_status == 'snatched' || new_status == 'seeding' || new_status == 'done'))
var q = self.addQuality(release.quality_id);
q = self.addQuality(release.quality_id);
if(q && !q.hasClass(new_status)) {
q.removeClass(release.status).addClass(new_status);
q.set('title', q.get('title').replace(release.status, new_status));
}
}
};
App.on('release.update_status', update_handle);
@ -391,12 +374,11 @@ MA.Release = new Class({
},
ignore: function(release){
var self = this;
Api.request('release.ignore', {
'data': {
'id': release._id
},
}
})
},
@ -456,7 +438,7 @@ MA.Trailer = new Class({
watch: function(offset){
var self = this;
var data_url = 'https://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18'
var data_url = 'https://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18';
var url = data_url.substitute({
'title': encodeURI(self.getTitle()),
'year': self.get('year'),
@ -515,7 +497,7 @@ MA.Trailer = new Class({
}
}
}
};
self.player.addEventListener('onStateChange', change_quality);
}
@ -532,7 +514,7 @@ MA.Trailer = new Class({
$(self.movie).setStyle('height', null);
setTimeout(function(){
self.container.destroy()
self.container.destroy();
self.close_button.destroy();
}, 1800)
}
@ -661,7 +643,7 @@ MA.Edit = new Class({
self.movie.slide('out');
}
})
});
MA.Refresh = new Class({
@ -847,7 +829,7 @@ MA.Files = new Class({
new Element('div.item.head').adopt(
new Element('span.name', {'text': 'File'}),
new Element('span.type', {'text': 'Type'})
).inject(self.files_container)
).inject(self.files_container);
if(self.movie.data.releases)
Array.each(self.movie.data.releases, function(release){

5
couchpotato/core/media/movie/_base/static/movie.css

@ -398,7 +398,6 @@
.movies .data .quality span {
padding: 2px 3px;
font-weight: bold;
opacity: 0.5;
font-size: 10px;
height: 16px;
@ -451,7 +450,6 @@
right: 20px;
line-height: 0;
top: 0;
display: block;
width: auto;
opacity: 0;
display: none;
@ -833,7 +831,6 @@
}
.movies .alph_nav .search input {
padding: 6px 5px;
width: 100%;
height: 44px;
display: inline-block;
@ -841,7 +838,6 @@
background: none;
color: #444;
font-size: 14px;
padding: 10px;
padding: 0 10px 0 30px;
border-bottom: 1px solid rgba(0,0,0,.08);
}
@ -1043,7 +1039,6 @@
}
.movies .progress > div .percentage {
font-weight: bold;
display: inline-block;
text-transform: uppercase;
font-weight: normal;

52
couchpotato/core/media/movie/_base/static/movie.js

@ -23,7 +23,7 @@ var Movie = new Class({
addEvents: function(){
var self = this;
self.global_events = {}
self.global_events = {};
// Do refresh with new data
self.global_events['movie.update'] = function(notification){
@ -32,7 +32,7 @@ var Movie = new Class({
self.busy(false);
self.removeView();
self.update.delay(2000, self, notification);
}
};
App.on('movie.update', self.global_events['movie.update']);
// Add spinner on load / search
@ -40,20 +40,20 @@ var Movie = new Class({
self.global_events[listener] = function(notification){
if(notification.data && (self.data._id == notification.data._id || (typeOf(notification.data._id) == 'array' && notification.data._id.indexOf(self.data._id) > -1)))
self.busy(true);
}
};
App.on(listener, self.global_events[listener]);
})
});
// Remove spinner
self.global_events['movie.searcher.ended'] = function(notification){
if(notification.data && self.data._id == notification.data._id)
self.busy(false)
}
};
App.on('movie.searcher.ended', self.global_events['movie.searcher.ended']);
// Reload when releases have updated
self.global_events['release.update_status'] = function(notification){
var data = notification.data
var data = notification.data;
if(data && self.data._id == data.movie_id){
if(!self.data.releases)
@ -62,7 +62,7 @@ var Movie = new Class({
self.data.releases.push({'quality': data.quality, 'status': data.status});
self.updateReleases();
}
}
};
App.on('release.update_status', self.global_events['release.update_status']);
@ -73,7 +73,7 @@ var Movie = new Class({
self.el.destroy();
delete self.list.movies_added[self.get('id')];
self.list.movies.erase(self)
self.list.movies.erase(self);
self.list.checkIfEmpty();
@ -117,18 +117,6 @@ var Movie = new Class({
}).inject(self.el, 'top').fade('hide');
},
positionMask: function(){
var self = this,
s = self.el.getSize()
return self.mask.setStyles({
'width': s.x,
'height': s.y
}).position({
'relativeTo': self.el
})
},
update: function(notification){
var self = this;
@ -197,7 +185,7 @@ var Movie = new Class({
if(self.profile.data)
self.profile.getTypes().each(function(type){
var q = self.addQuality(type.get('quality'));
var q = self.addQuality(type.get('quality'), type.get('3d'));
if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){
q.addClass('finish');
q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.')
@ -209,7 +197,7 @@ var Movie = new Class({
self.updateReleases();
Object.each(self.options.actions, function(action, key){
self.action[key.toLowerCase()] = action = new self.options.actions[key](self)
self.action[key.toLowerCase()] = action = new self.options.actions[key](self);
if(action.el)
self.actions.adopt(action)
});
@ -222,11 +210,11 @@ var Movie = new Class({
self.data.releases.each(function(release){
var q = self.quality.getElement('.q_'+ release.quality),
var q = self.quality.getElement('.q_'+ release.quality+(release.is_3d ? '.is_3d' : ':not(.is_3d)')),
status = release.status;
if(!q && (status == 'snatched' || status == 'seeding' || status == 'done'))
var q = self.addQuality(release.quality)
q = self.addQuality(release.quality, release.is_3d || false);
if (q && !q.hasClass(status)){
q.addClass(status);
@ -236,13 +224,13 @@ var Movie = new Class({
});
},
addQuality: function(quality){
addQuality: function(quality, is_3d){
var self = this;
var q = Quality.getQuality(quality);
return new Element('span', {
'text': q.label,
'class': 'q_'+q.identifier,
'text': q.label + (is_3d ? ' 3D' : ''),
'class': 'q_'+q.identifier + (is_3d ? ' is_3d' : ''),
'title': ''
}).inject(self.quality);
@ -252,9 +240,9 @@ var Movie = new Class({
var self = this;
if(self.data.title)
return self.getUnprefixedTitle(self.data.title)
return self.getUnprefixedTitle(self.data.title);
else if(self.data.info.titles.length > 0)
return self.getUnprefixedTitle(self.data.info.titles[0])
return self.getUnprefixedTitle(self.data.info.titles[0]);
return 'Unknown movie'
},
@ -275,12 +263,12 @@ var Movie = new Class({
self.el.addEvent('outerClick', function(){
self.removeView();
self.slide('out')
})
});
el.show();
self.data_container.addClass('hide_right');
}
else {
self.el.removeEvents('outerClick')
self.el.removeEvents('outerClick');
setTimeout(function(){
if(self.el)
@ -297,7 +285,7 @@ var Movie = new Class({
if(self.el)
self.el
.removeClass(self.view+'_view')
.addClass(new_view+'_view')
.addClass(new_view+'_view');
self.view = new_view;
},

18
couchpotato/core/media/movie/_base/static/search.js

@ -41,7 +41,7 @@ Block.Search.MovieItem = new Class({
)
)
)
)
);
if(info.titles)
info.titles.each(function(title){
@ -132,19 +132,19 @@ Block.Search.MovieItem = new Class({
if(!self.options_el.hasClass('set')){
if(self.info.in_library){
if(info.in_library){
var in_library = [];
(self.info.in_library.releases || []).each(function(release){
(info.in_library.releases || []).each(function(release){
in_library.include(release.quality)
});
}
self.options_el.grab(
new Element('div', {
'class': self.info.in_wanted && self.info.in_wanted.profile_id || in_library ? 'in_library_wanted' : ''
'class': info.in_wanted && info.in_wanted.profile_id || in_library ? 'in_library_wanted' : ''
}).adopt(
self.info.in_wanted && self.info.in_wanted.profile_id ? new Element('span.in_wanted', {
'text': 'Already in wanted list: ' + Quality.getProfile(self.info.in_wanted.profile_id).get('label')
info.in_wanted && info.in_wanted.profile_id ? new Element('span.in_wanted', {
'text': 'Already in wanted list: ' + Quality.getProfile(info.in_wanted.profile_id).get('label')
}) : (in_library ? new Element('span.in_library', {
'text': 'Already in library: ' + in_library.join(', ')
}) : null),
@ -172,7 +172,7 @@ Block.Search.MovieItem = new Class({
new Element('option', {
'text': alt.title
}).inject(self.title_select)
})
});
// Fill categories
@ -215,9 +215,9 @@ Block.Search.MovieItem = new Class({
loadingMask: function(){
var self = this;
self.mask = new Element('div.mask').inject(self.el).fade('hide')
self.mask = new Element('div.mask').inject(self.el).fade('hide');
createSpinner(self.mask)
createSpinner(self.mask);
self.mask.fade('in')
},

34
couchpotato/core/media/movie/charts/__init__.py

@ -0,0 +1,34 @@
from .main import Charts
def autoload():
return Charts()
config = [{
'name': 'charts',
'groups': [
{
'label': 'Charts',
'description': 'Displays selected charts on the home page',
'type': 'list',
'name': 'charts_providers',
'tab': 'display',
'options': [
{
'name': 'max_items',
'default': 5,
'type': 'int',
'description': 'Maximum number of items displayed from each chart.',
},
{
'name': 'update_interval',
'default': 12,
'type': 'int',
'advanced': True,
'description': '(hours)',
},
],
},
],
}]

58
couchpotato/core/media/movie/charts/main.py

@ -0,0 +1,58 @@
import time
from couchpotato import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent,fireEvent
from couchpotato.core.plugins.base import Plugin
log = CPLog(__name__)
class Charts(Plugin):
update_in_progress = False
def __init__(self):
addApiView('charts.view', self.automationView)
addEvent('app.load', self.setCrons)
def setCrons(self):
fireEvent('schedule.interval', 'charts.update_cache', self.updateViewCache, hours = self.conf('update_interval', default = 12))
def automationView(self, force_update = False, **kwargs):
if force_update:
charts = self.updateViewCache()
else:
charts = self.getCache('charts_cached')
if not charts:
charts = self.updateViewCache()
return {
'success': True,
'count': len(charts),
'charts': charts
}
def updateViewCache(self):
if self.update_in_progress:
while self.update_in_progress:
time.sleep(1)
catched_charts = self.getCache('charts_cached')
if catched_charts:
return catched_charts
try:
self.update_in_progress = True
charts = fireEvent('automation.get_chart_list', merge = True)
self.setCache('charts_cached', charts, timeout = 7200 * tryInt(self.conf('update_interval', default = 12)))
except:
log.error('Failed refreshing charts')
self.update_in_progress = False
return charts

261
couchpotato/core/media/movie/charts/static/charts.css

@ -0,0 +1,261 @@
.charts {
clear: both;
margin-bottom: 30px;
}
.charts > h2 {
height: 40px;
}
.charts .chart {
display: inline-block;
width: 50%;
vertical-align: top;
}
.charts .refresh {
clear:both;
position: relative;
}
.charts .refresh .refreshing {
display: block;
padding: 20px;
font-size: 20px;
text-align:center;
}
.charts .refresh a {
text-align: center;
padding: 0;
display: none;
width: 30px;
height: 30px;
position: absolute;
right: 10px;
top: -40px;
opacity: .7;
}
.charts .refresh a:hover {
opacity: 1;
}
.charts p.no_charts_enabled {
padding: 0.7em 1em;
display: none;
}
.charts .chart h3 a {
color: #fff;
}
.charts .chart .media_result {
display: inline-block;
width: 100%;
height: 150px;
}
@media all and (max-width: 960px) {
.charts .chart {
width: 50%;
}
}
@media all and (max-width: 600px) {
.charts .chart {
width: 100%;
}
}
.charts .chart .media_result .data {
left: 150px;
background: #4e5969;
border: none;
}
.charts .chart .media_result .data .info {
top: 10px;
left: 15px;
right: 15px;
bottom: 10px;
overflow: hidden;
}
.charts .chart .media_result .data .info h2 {
white-space: normal;
max-height: 120px;
font-size: 18px;
line-height: 18px;
}
.charts .chart .media_result .data .info .rating,
.charts .chart .media_result .data .info .genres,
.charts .chart .media_result .data .info .year {
position: static;
display: block;
padding: 0;
opacity: .6;
}
.charts .chart .media_result .data .info .year {
margin: 10px 0 0;
}
.charts .chart .media_result .data .info .rating {
font-size: 20px;
float: right;
margin-top: -20px;
}
.charts .chart .media_result .data .info .rating:before {
content: "\e031";
font-family: 'Elusive-Icons';
font-size: 14px;
margin: 0 5px 0 0;
vertical-align: bottom;
}
.charts .chart .media_result .data .info .genres {
font-size: 11px;
font-style: italic;
text-align: right;
}
.charts .chart .media_result .data .info .plot {
display: block;
font-size: 11px;
overflow: hidden;
text-align: justify;
height: 100%;
z-index: 2;
top: 64px;
position: absolute;
background: #4e5969;
cursor: pointer;
transition: all .4s ease-in-out;
padding: 0 3px 10px 0;
}
.charts .chart .media_result .data:before {
bottom: 0;
content: '';
display: block;
height: 10px;
right: 0;
left: 0;
bottom: 10px;
position: absolute;
background: linear-gradient(
0deg,
rgba(78, 89, 105, 1) 0%,
rgba(78, 89, 105, 0) 100%
);
z-index: 3;
pointer-events: none;
}
.charts .chart .media_result .data .info .plot.full {
top: 0;
overflow: auto;
}
.charts .chart .media_result .data {
cursor: default;
}
.charts .chart .media_result .options {
left: 150px;
}
.charts .chart .media_result .options select[name=title] { width: 100%; }
.charts .chart .media_result .options select[name=profile] { width: 100%; }
.charts .chart .media_result .options select[name=category] { width: 100%; }
.charts .chart .media_result .button {
position: absolute;
margin: 2px 0 0 0;
right: 15px;
bottom: 15px;
}
.charts .chart .media_result .thumbnail {
width: 100px;
position: absolute;
left: 50px;
}
.charts .chart .media_result .chart_number {
color: white;
position: absolute;
top: 0;
padding: 10px;
font: bold 2em/1em Helvetica, Sans-Serif;
width: 50px;
height: 100%;
text-align: center;
border-left: 8px solid transparent;
}
.charts .chart .media_result.chart_in_wanted .chart_number {
border-color: rgba(0, 255, 40, 0.3);
}
.charts .chart .media_result.chart_in_library .chart_number {
border-color: rgba(0, 202, 32, 0.3);
}
.charts .chart .media_result .actions {
position: absolute;
top: 10px;
right: 10px;
display: none;
width: 90px;
}
.charts .chart .media_result:hover .actions {
display: block;
}
.charts .chart .media_result:hover h2 .title {
opacity: 0;
}
.charts .chart .media_result .data.open .actions {
display: none;
}
.charts .chart .media_result .actions a {
margin-left: 10px;
vertical-align: middle;
}
.toggle_menu {
height: 50px;
}
.toggle_menu a {
display: block;
width: 50%;
float: left;
color: rgba(255,255,255,.6);
border-bottom: 1px solid rgba(255, 255, 255, 0.0666667);
}
.toggle_menu a:hover {
border-color: #047792;
border-width: 4px;
color: #fff;
}
.toggle_menu a.active {
border-bottom: 4px solid #04bce6;
color: #fff;
}
.toggle_menu a:last-child {
float: right;
}
.toggle_menu h2 {
height: 40px;
}

170
couchpotato/core/media/movie/charts/static/charts.js

@ -0,0 +1,170 @@
var Charts = new Class({
Implements: [Options, Events],
initialize: function(options){
var self = this;
self.setOptions(options);
self.create();
},
create: function(){
var self = this;
self.el = new Element('div.charts').adopt(
self.el_no_charts_enabled = new Element('p.no_charts_enabled', {
'html': 'Hey, it looks like you have no charts enabled at the moment. If you\'d like some great movie suggestions you can go to <a href="' + App.createUrl('settings/display') + '">settings</a> and turn on some charts of your choice.'
}),
self.el_refresh_container = new Element('div.refresh').adopt(
self.el_refresh_link = new Element('a.refresh.icon2', {
'href': '#',
'events': {
'click': function(e) {
e.preventDefault();
self.el.getChildren('div.chart').destroy();
self.el_refreshing_text.show();
self.el_refresh_link.hide();
self.api_request = Api.request('charts.view', {
'data': { 'force_update': 1 },
'onComplete': self.fill.bind(self)
});
}
}
}),
self.el_refreshing_text = new Element('span.refreshing', {
'text': 'Refreshing charts...'
})
)
);
if( Cookie.read('suggestions_charts_menu_selected') === 'charts')
self.el.show();
else
self.el.hide();
self.api_request = Api.request('charts.view', {
'onComplete': self.fill.bind(self)
});
self.fireEvent.delay(0, self, 'created');
},
fill: function(json){
var self = this;
self.el_refreshing_text.hide();
self.el_refresh_link.show();
if(!json || json.count == 0){
self.el_no_charts_enabled.show();
self.el_refresh_link.show();
self.el_refreshing_text.hide();
}
else {
self.el_no_charts_enabled.hide();
json.charts.sort(function(a, b) {
return a.order - b.order;
});
Object.each(json.charts, function(chart){
var c = new Element('div.chart').grab(
new Element('h3').grab( new Element('a', {
'text': chart.name,
'href': chart.url
}))
);
var it = 1;
Object.each(chart.list, function(movie){
var m = new Block.Search.MovieItem(movie, {
'onAdded': function(){
self.afterAdded(m, movie)
}
});
var in_database_class = movie.in_wanted ? 'chart_in_wanted' : (movie.in_library ? 'chart_in_library' : ''),
in_database_title = movie.in_wanted ? 'Movie in wanted list' : (movie.in_library ? 'Movie in library' : '');
m.el
.addClass(in_database_class)
.grab(
new Element('div.chart_number', {
'text': it++,
'title': in_database_title
})
);
m.data_container.grab(
new Element('div.actions').adopt(
new Element('a.add.icon2', {
'title': 'Add movie with your default quality',
'data-add': movie.imdb,
'events': {
'click': m.showOptions.bind(m)
}
}),
$(new MA.IMDB(m)),
$(new MA.Trailer(m, {
'height': 150
}))
)
);
m.data_container.removeEvents('click');
var plot = false;
if(m.info.plot && m.info.plot.length > 0)
plot = m.info.plot;
// Add rating
m.info_container.adopt(
m.rating = m.info.rating && m.info.rating.imdb && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', {
'text': parseFloat(m.info.rating.imdb[0]),
'title': parseInt(m.info.rating.imdb[1]) + ' votes'
}) : null,
m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', {
'text': m.info.genres.slice(0, 3).join(', ')
}) : null,
m.plot = plot ? new Element('span.plot', {
'text': plot,
'events': {
'click': function(){
this.toggleClass('full')
}
}
}) : null
);
$(m).inject(c);
});
c.inject(self.el);
});
}
self.fireEvent('loaded');
},
afterAdded: function(m){
$(m).getElement('div.chart_number')
.addClass('chart_in_wanted')
.set('title', 'Movie in wanted list');
},
toElement: function(){
return this.el;
}
});

13
couchpotato/core/media/movie/providers/automation/base.py

@ -13,6 +13,7 @@ log = CPLog(__name__)
class Automation(AutomationBase):
enabled_option = 'automation_enabled'
chart_enabled_option = 'chart_display_enabled'
http_time_between_calls = 2
interval = 1800
@ -20,6 +21,7 @@ class Automation(AutomationBase):
def __init__(self):
addEvent('automation.get_movies', self._getMovies)
addEvent('automation.get_chart_list', self._getChartList)
def _getMovies(self):
@ -34,6 +36,13 @@ class Automation(AutomationBase):
return self.getIMDBids()
def _getChartList(self):
if not (self.conf(self.chart_enabled_option) or self.conf(self.chart_enabled_option) is None):
return
return self.getChartList()
def search(self, name, year = None, imdb_only = False):
prop_name = 'automation.cached.%s.%s' % (name, year)
@ -94,5 +103,9 @@ class Automation(AutomationBase):
def getIMDBids(self):
return []
def getChartList(self):
# Example return: [ {'name': 'Display name of list', 'url': 'http://example.com/', 'order': 1, 'list': []} ]
return
def canCheck(self):
return time.time() > self.last_checked + self.interval

44
couchpotato/core/media/movie/providers/automation/bluray.py

@ -14,6 +14,8 @@ class Bluray(Automation, RSS):
interval = 1800
rss_url = 'http://www.blu-ray.com/rss/newreleasesfeed.xml'
backlog_url = 'http://www.blu-ray.com/movies/movies.php?show=newreleases&page=%s'
display_url = 'http://www.blu-ray.com/movies/movies.php?show=newreleases'
chart_order = 1
def getIMDBids(self):
@ -62,7 +64,7 @@ class Bluray(Automation, RSS):
name = self.getTextElement(movie, 'title').lower().split('blu-ray')[0].strip('(').rstrip()
year = self.getTextElement(movie, 'description').split('|')[1].strip('(').strip()
if not name.find('/') == -1: # make sure it is not a double movie release
if not name.find('/') == -1: # make sure it is not a double movie release
continue
if tryInt(year) < self.getMinimal('year'):
@ -77,6 +79,32 @@ class Bluray(Automation, RSS):
return movies
def getChartList(self):
# 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': []}
max_items = int(self.conf('max_items', section='charts', default=5))
rss_movies = self.getRSSData(self.rss_url)
for movie in rss_movies:
name = self.getTextElement(movie, 'title').lower().split('blu-ray')[0].strip('(').rstrip()
year = self.getTextElement(movie, 'description').split('|')[1].strip('(').strip()
if not name.find('/') == -1: # make sure it is not a double movie release
continue
movie = self.search(name, year)
if movie:
movie_list['list'].append( movie )
if len(movie_list['list']) >= max_items:
break
if not movie_list['list']:
return
return [ movie_list ]
config = [{
'name': 'bluray',
'groups': [
@ -101,5 +129,19 @@ config = [{
},
],
},
{
'tab': 'display',
'list': 'charts_providers',
'name': 'bluray_charts_display',
'label': 'Blu-ray.com',
'description': 'Display <a href="http://www.blu-ray.com/movies/movies.php?show=newreleases">new releases</a> from Blu-ray.com',
'options': [
{
'name': 'chart_display_enabled',
'default': True,
'type': 'enabler',
},
],
},
],
}]

5
couchpotato/core/media/movie/providers/automation/goodfilms.py

@ -46,7 +46,10 @@ class Goodfilms(Automation):
break
for movie in this_watch_list:
movies.append({ 'title': movie['data-film-title'], 'year': movie['data-film-year'] })
movies.append({
'title': movie['data-film-title'],
'year': movie['data-film-year']
})
if not 'next page' in data.lower():
break

85
couchpotato/core/media/movie/providers/automation/imdb.py

@ -105,6 +105,16 @@ class IMDBAutomation(IMDBBase):
'top250': 'http://www.imdb.com/chart/top',
'boxoffice': 'http://www.imdb.com/chart/',
}
chart_names = {
'theater': 'IMDB - Movies in Theaters',
'top250': 'IMDB - Top 250 Movies',
'boxoffice': 'IMDB - Box Office',
}
chart_order = {
'theater': 2,
'top250': 4,
'boxoffice': 3,
}
first_table = ['boxoffice']
@ -144,6 +154,46 @@ class IMDBAutomation(IMDBBase):
return movies
def getChartList(self):
# Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id)
movie_lists = []
max_items = int(self.conf('max_items', section='charts', default=5))
for url in self.chart_urls:
if self.conf('chart_display_%s' % url):
movie_list = {'name': self.chart_names[url], 'url': self.chart_urls[url], 'order': self.chart_order[url], 'list': []}
data = self.getHTMLData(self.chart_urls[url])
if data:
html = BeautifulSoup(data)
try:
result_div = html.find('div', attrs = {'id': 'main'})
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)
for imdb_id in imdb_ids[0:max_items]:
info = self.getInfo(imdb_id)
movie_list['list'].append(info)
if self.shuttingDown():
break
except:
log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc()))
if movie_list['list']:
movie_lists.append(movie_list)
return movie_lists
config = [{
'name': 'imdb',
'groups': [
@ -206,5 +256,40 @@ config = [{
},
],
},
{
'tab': 'display',
'list': 'charts_providers',
'name': 'imdb_charts_display',
'label': 'IMDB',
'description': 'Display movies from IMDB Charts',
'options': [
{
'name': 'chart_display_enabled',
'default': True,
'type': 'enabler',
},
{
'name': 'chart_display_theater',
'type': 'bool',
'label': 'In Theaters',
'description': 'New Movies <a href="http://www.imdb.com/movies-in-theaters/">In-Theaters</a> chart',
'default': False,
},
{
'name': 'chart_display_top250',
'type': 'bool',
'label': 'TOP 250',
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
'default': False,
},
{
'name': 'chart_display_boxoffice',
'type': 'bool',
'label': 'Box office TOP 10',
'description': 'IMDB Box office <a href="http://www.imdb.com/chart/">TOP 10</a> chart',
'default': True,
},
],
},
],
}]

5
couchpotato/core/media/movie/providers/automation/letterboxd.py

@ -50,7 +50,10 @@ class Letterboxd(Automation):
for movie in soup.find_all('a', attrs = {'class': 'frame'}):
match = removeEmpty(self.pattern.split(movie['title']))
movies.append({'title': match[0], 'year': match[1] })
movies.append({
'title': match[0],
'year': match[1]
})
return movies

3
couchpotato/core/media/movie/providers/info/_modifier.py

@ -104,6 +104,9 @@ class MovieResultModifier(Plugin):
if media.get('status') == 'active':
temp['in_wanted'] = media
try: temp['in_wanted']['profile'] = db.get('id', media['profile_id'])
except: temp['in_wanted']['profile'] = {'label': ''}
for release in fireEvent('release.for_media', media['_id'], single = True):
if release.get('status') == 'done':
if not temp['in_library']:

1
couchpotato/core/media/movie/providers/info/themoviedb.py

@ -74,6 +74,7 @@ class TheMovieDb(MovieProvider):
if not result:
try:
log.debug('Getting info: %s', cache_key)
# noinspection PyArgumentList
movie = tmdb3.Movie(identifier)
try: exists = movie.title is not None
except: exists = False

2
couchpotato/core/media/movie/providers/torrent/bithdtv.py

@ -14,6 +14,6 @@ class BiTHDTV(MovieProvider, Base):
def buildUrl(self, media):
query = tryUrlencode({
'search': fireEvent('library.query', media, single = True),
'cat': 7 # Movie cat
'cat': 7 # Movie cat
})
return query

3
couchpotato/core/media/movie/providers/torrent/bitsoup.py

@ -11,6 +11,7 @@ autoload = 'Bitsoup'
class Bitsoup(MovieProvider, Base):
cat_ids = [
([17], ['3d']),
([41], ['720p', '1080p']),
([20], ['dvdr']),
([19], ['brrip', 'dvdrip']),
@ -23,6 +24,6 @@ class Bitsoup(MovieProvider, Base):
fireEvent('library.query', media, include_year = False, single = True),
media['info']['year']
),
'cat': self.getCatId(quality['identifier'])[0],
'cat': self.getCatId(quality)[0],
})
return query

3
couchpotato/core/media/movie/providers/torrent/iptorrents.py

@ -10,6 +10,7 @@ autoload = 'IPTorrents'
class IPTorrents(MovieProvider, Base):
cat_ids = [
([87], ['3d']),
([48], ['720p', '1080p', 'bd50']),
([72], ['cam', 'ts', 'tc', 'r5', 'scr']),
([7], ['dvdrip', 'brrip']),
@ -19,4 +20,4 @@ class IPTorrents(MovieProvider, Base):
def buildUrl(self, title, media, quality):
query = '%s %s' % (title.replace(':', ''), media['info']['year'])
return self._buildUrl(query, quality['identifier'])
return self._buildUrl(query, quality)

44
couchpotato/core/media/movie/providers/torrent/passthepopcorn.py

@ -10,29 +10,29 @@ autoload = 'PassThePopcorn'
class PassThePopcorn(MovieProvider, Base):
quality_search_params = {
'bd50': {'media': 'Blu-ray', 'format': 'BD50'},
'1080p': {'resolution': '1080p'},
'720p': {'resolution': '720p'},
'brrip': {'media': 'Blu-ray'},
'dvdr': {'resolution': 'anysd'},
'dvdrip': {'media': 'DVD'},
'scr': {'media': 'DVD-Screener'},
'r5': {'media': 'R5'},
'tc': {'media': 'TC'},
'ts': {'media': 'TS'},
'cam': {'media': 'CAM'}
'bd50': {'media': 'Blu-ray', 'format': 'BD50'},
'1080p': {'resolution': '1080p'},
'720p': {'resolution': '720p'},
'brrip': {'media': 'Blu-ray'},
'dvdr': {'resolution': 'anysd'},
'dvdrip': {'media': 'DVD'},
'scr': {'media': 'DVD-Screener'},
'r5': {'media': 'R5'},
'tc': {'media': 'TC'},
'ts': {'media': 'TS'},
'cam': {'media': 'CAM'}
}
post_search_filters = {
'bd50': {'Codec': ['BD50']},
'1080p': {'Resolution': ['1080p']},
'720p': {'Resolution': ['720p']},
'brrip': {'Source': ['Blu-ray'], 'Quality': ['High Definition'], 'Container': ['!ISO']},
'dvdr': {'Codec': ['DVD5', 'DVD9']},
'dvdrip': {'Source': ['DVD'], 'Codec': ['!DVD5', '!DVD9']},
'scr': {'Source': ['DVD-Screener']},
'r5': {'Source': ['R5']},
'tc': {'Source': ['TC']},
'ts': {'Source': ['TS']},
'cam': {'Source': ['CAM']}
'bd50': {'Codec': ['BD50']},
'1080p': {'Resolution': ['1080p']},
'720p': {'Resolution': ['720p']},
'brrip': {'Source': ['Blu-ray'], 'Quality': ['High Definition'], 'Container': ['!ISO']},
'dvdr': {'Codec': ['DVD5', 'DVD9']},
'dvdrip': {'Source': ['DVD'], 'Codec': ['!DVD5', '!DVD9']},
'scr': {'Source': ['DVD-Screener']},
'r5': {'Source': ['R5']},
'tc': {'Source': ['TC']},
'ts': {'Source': ['TS']},
'cam': {'Source': ['CAM']}
}

6
couchpotato/core/media/movie/providers/torrent/sceneaccess.py

@ -18,10 +18,8 @@ class SceneAccess(MovieProvider, Base):
]
def buildUrl(self, media, quality):
url = self.urls['search'] % (
self.getCatId(quality['identifier'])[0],
self.getCatId(quality['identifier'])[0]
)
cat_id = self.getCatId(quality)[0]
url = self.urls['search'] % (cat_id, cat_id)
arguments = tryUrlencode({
'search': fireEvent('library.query', media, single = True),

9
couchpotato/core/media/movie/providers/torrent/thepiratebay.py

@ -12,10 +12,11 @@ autoload = 'ThePirateBay'
class ThePirateBay(MovieProvider, Base):
cat_ids = [
([207], ['720p', '1080p']),
([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
([201, 207], ['brrip']),
([202], ['dvdr'])
([209], ['3d']),
([207], ['720p', '1080p']),
([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
([201, 207], ['brrip']),
([202], ['dvdr'])
]
def buildUrl(self, media, page, cats):

1
couchpotato/core/media/movie/providers/torrent/torrentday.py

@ -16,5 +16,6 @@ class TorrentDay(MovieProvider, Base):
([3], ['dvdr']),
([5], ['bd50']),
]
def buildUrl(self, media):
return fireEvent('library.query', media, single = True)

2
couchpotato/core/media/movie/providers/torrent/torrentleech.py

@ -24,5 +24,5 @@ class TorrentLeech(MovieProvider, Base):
def buildUrl(self, media, quality):
return (
tryUrlencode(fireEvent('library.query', media, single = True)),
self.getCatId(quality['identifier'])[0]
self.getCatId(quality)[0]
)

2
couchpotato/core/media/movie/providers/torrent/torrentshack.py

@ -31,6 +31,6 @@ class TorrentShack(MovieProvider, Base):
def buildUrl(self, media, quality):
query = (tryUrlencode(fireEvent('library.query', media, single = True)),
self.getCatId(quality['identifier'])[0],
self.getCatId(quality)[0],
self.getSceneOnly())
return query

15
couchpotato/core/media/movie/providers/torrent/torrentz.py

@ -0,0 +1,15 @@
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.logger import CPLog
from couchpotato.core.event import fireEvent
from couchpotato.core.media._base.providers.torrent.torrentz import Base
from couchpotato.core.media.movie.providers.base import MovieProvider
log = CPLog(__name__)
autoload = 'Torrentz'
class Torrentz(MovieProvider, Base):
def buildUrl(self, media):
return tryUrlencode('"%s"' % fireEvent('library.query', media, single = True))

2
couchpotato/core/media/movie/providers/trailer/hdtrailers.py

@ -52,7 +52,7 @@ class HDTrailers(TrailerProvider):
return result_data
def findViaAlternative(self, group):
results = {'480p':[], '720p':[], '1080p':[]}
results = {'480p': [], '720p': [], '1080p': []}
movie_name = getTitle(group)

8
couchpotato/core/media/movie/providers/userscript/filmcentrum.py

@ -0,0 +1,8 @@
from couchpotato.core.media._base.providers.userscript.base import UserscriptBase
autoload = 'FilmCentrum'
class FilmCentrum(UserscriptBase):
includes = ['*://filmcentrum.nl/films/*']

17
couchpotato/core/media/movie/searcher.py

@ -143,14 +143,17 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
ret = False
index = 0
for q_identifier in profile.get('qualities'):
index = profile['qualities'].index(q_identifier)
quality_custom = {
'quality': q_identifier,
'finish': profile['finish'][index],
'wait_for': profile['wait_for'][index]
'wait_for': profile['wait_for'][index],
'3d': profile['3d'][index] if profile.get('3d') else False
}
index += 1
if not self.conf('always_search') and not self.couldBeReleased(q_identifier in pre_releases, release_dates, movie['info']['year']):
too_early_to_search.append(q_identifier)
continue
@ -168,6 +171,9 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
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']))
@ -221,13 +227,17 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
if not fireEvent('searcher.correct_words', nzb['name'], media, single = True):
return False
preferred_quality = 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
if 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']))
return False
# Contains lower quality string
if not fireEvent('searcher.correct_3d', nzb, preferred_quality = preferred_quality, single = True):
log.info2('Wrong: %s, %slooking for %s in 3D', (nzb['name'], ('' if preferred_quality['custom'].get('3d') else 'NOT '), quality['label']))
return False
# File to small
if nzb['size'] and tryInt(preferred_quality['size_min']) > tryInt(nzb['size']):
@ -239,7 +249,6 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
log.info2('Wrong: "%s" is too large to be %s. %sMB instead of the maximum of %sMB.', (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_max']))
return False
# Provider specific functions
get_more = nzb.get('get_more_info')
if get_more:

12
couchpotato/core/media/movie/suggestion/static/suggest.css

@ -1,4 +1,7 @@
.suggestions {
clear: both;
padding-top: 10px;
margin-bottom: 30px;
}
.suggestions > h2 {
@ -91,7 +94,6 @@
padding: 0 3px 10px 0;
}
.suggestions .media_result .data:before {
bottom: 0;
content: '';
display: block;
height: 10px;
@ -107,7 +109,7 @@
z-index: 3;
pointer-events: none;
}
.suggestions .media_result .data .info .plot.full {
top: 0;
overflow: auto;
@ -123,14 +125,14 @@
.suggestions .media_result .options select[name=title] { width: 100%; }
.suggestions .media_result .options select[name=profile] { width: 100%; }
.suggestions .media_result .options select[name=category] { width: 100%; }
.suggestions .media_result .button {
.suggestions .media_result .button {
position: absolute;
margin: 2px 0 0 0;
right: 15px;
bottom: 15px;
}
.suggestions .media_result .thumbnail {
width: 100px;

13
couchpotato/core/media/movie/suggestion/static/suggest.js

@ -42,11 +42,10 @@ var SuggestList = new Class({
}
}
}).grab(
new Element('h2', {
'text': 'You might like these'
})
);
});
var cookie_menu_select = Cookie.read('suggestions_charts_menu_selected');
if( cookie_menu_select === 'suggestions' || cookie_menu_select === null ) self.el.show(); else self.el.hide();
self.api_request = Api.request('suggestion.view', {
'onComplete': self.fill.bind(self)
@ -116,7 +115,7 @@ var SuggestList = new Class({
}
}
}) : null
)
);
$(m).inject(self.el);
@ -150,4 +149,4 @@ var SuggestList = new Class({
return this.el;
}
})
});

2
couchpotato/core/notifications/core/main.py

@ -131,7 +131,7 @@ class CoreNotifier(Notification):
for message in messages:
if message.get('time') > last_check:
message['sticky'] = True # Always sticky core messages
message['sticky'] = True # Always sticky core messages
message_type = 'core.message.important' if message.get('important') else 'core.message'
fireEvent(message_type, message = message.get('message'), data = message)

26
couchpotato/core/notifications/core/static/notification.js

@ -17,14 +17,14 @@ var NotificationBase = new Class({
App.addEvent('load', self.addTestButtons.bind(self));
// Notification bar
self.notifications = []
self.notifications = [];
App.addEvent('load', function(){
App.block.notification = new Block.Menu(self, {
'button_class': 'icon2.eye-open',
'class': 'notification_menu',
'onOpen': self.markAsRead.bind(self)
})
});
$(App.block.notification).inject(App.getBlock('search'), 'after');
self.badge = new Element('div.badge').inject(App.block.notification, 'top').hide();
@ -40,7 +40,7 @@ var NotificationBase = new Class({
var self = this;
var added = new Date();
added.setTime(result.added*1000)
added.setTime(result.added*1000);
result.el = App.getBlock('notification').addLink(
new Element('span.'+(result.read ? 'read' : '' )).adopt(
@ -51,7 +51,7 @@ var NotificationBase = new Class({
self.notifications.include(result);
if((result.data.important !== undefined || result.data.sticky !== undefined) && !result.read){
var sticky = true
var sticky = true;
App.trigger('message', [result.message, sticky, result])
}
else if(!result.read){
@ -62,7 +62,7 @@ var NotificationBase = new Class({
setBadge: function(value){
var self = this;
self.badge.set('text', value)
self.badge.set('text', value);
self.badge[value ? 'show' : 'hide']()
},
@ -73,9 +73,9 @@ var NotificationBase = new Class({
if(!force_ids) {
var rn = self.notifications.filter(function(n){
return !n.read && n.data.important === undefined
})
});
var ids = []
var ids = [];
rn.each(function(n){
ids.include(n._id)
})
@ -106,7 +106,7 @@ var NotificationBase = new Class({
'onSuccess': function(json){
self.processData(json, true)
}
}).send()
}).send();
setInterval(function(){
@ -141,7 +141,7 @@ var NotificationBase = new Class({
stopPoll: function(){
if(this.request)
this.request.cancel()
this.request.cancel();
this.stopped = true;
},
@ -154,7 +154,7 @@ var NotificationBase = new Class({
App.trigger(result._t || result.type, [result]);
if(result.message && result.read === undefined && !init)
self.showMessage(result.message);
})
});
if(json.result.length > 0)
self.last_id = json.result.getLast().message_id
@ -180,11 +180,11 @@ var NotificationBase = new Class({
}, 10);
var hide_message = function(){
new_message.addClass('hide')
new_message.addClass('hide');
setTimeout(function(){
new_message.destroy();
}, 1000);
}
};
if(sticky)
new_message.grab(
@ -206,7 +206,7 @@ var NotificationBase = new Class({
addTestButtons: function(){
var self = this;
var setting_page = App.getPage('Settings')
var setting_page = App.getPage('Settings');
setting_page.addEvent('create', function(){
Object.each(setting_page.tabs.notifications.groups, self.addTestButton.bind(self))
})

3
couchpotato/core/notifications/email_.py

@ -97,7 +97,8 @@ config = [{
'name': 'smtp_server',
'label': 'SMTP server',
},
{ 'name': 'smtp_port',
{
'name': 'smtp_port',
'label': 'SMTP server port',
'default': '25',
'type': 'int',

2
couchpotato/core/notifications/growl.py

@ -19,6 +19,8 @@ class Growl(Notification):
def __init__(self):
super(Growl, self).__init__()
self.growl = None
if self.isEnabled():
addEvent('app.load', self.register)

1
couchpotato/core/notifications/nmj.py

@ -20,6 +20,7 @@ autoload = 'NMJ'
class NMJ(Notification):
# noinspection PyMissingConstructor
def __init__(self):
addEvent('renamer.after', self.addToLibrary)
addApiView(self.testNotifyName(), self.test)

2
couchpotato/core/notifications/pushalot.py

@ -30,7 +30,7 @@ class Pushalot(Notification):
}
headers = {
'Content-type': 'application/x-www-form-urlencoded'
'Content-type': 'application/x-www-form-urlencoded'
}
try:

13
couchpotato/core/notifications/pushover.py

@ -25,6 +25,7 @@ class Pushover(Notification):
'token': self.app_token,
'message': toUnicode(message),
'priority': self.conf('priority'),
'sound': self.conf('sound'),
}
if data and data.get('identifier'):
@ -33,10 +34,9 @@ class Pushover(Notification):
'url_title': toUnicode('%s on IMDb' % getTitle(data)),
})
http_handler.request('POST',
"/1/messages.json",
headers = {'Content-type': 'application/x-www-form-urlencoded'},
body = tryUrlencode(api_data)
http_handler.request('POST', '/1/messages.json',
headers = {'Content-type': 'application/x-www-form-urlencoded'},
body = tryUrlencode(api_data)
)
response = http_handler.getresponse()
@ -83,6 +83,11 @@ config = [{
'advanced': True,
'description': 'Also send message when movie is snatched.',
},
{
'name': 'sound',
'advanced': True,
'description': 'Define <a href="https://pushover.net/api%23sounds" target="_blank">custom sound</a> for Pushover alert.'
},
],
}
],

2
couchpotato/core/notifications/twitter/static/twitter.js

@ -59,7 +59,7 @@ var TwitterNotification = new Class({
).inject(fieldset.getElement('.test_button'), 'before');
})
},
}
});

2
couchpotato/core/notifications/xbmc.py

@ -89,7 +89,7 @@ class XBMC(Notification):
self.use_json_notifications[host] = False
# send the text message
resp = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
resp = self.notifyXBMCnoJSON(host, {'title': self.default_title, 'message': message})
for r in resp:
if r.get('result') and r['result'] == 'OK':
log.debug('Message delivered successfully!')

4
couchpotato/core/plugins/automation.py

@ -35,9 +35,9 @@ class Automation(Plugin):
prop_name = 'automation.added.%s' % imdb_id
added = Env.prop(prop_name, default = False)
if not added:
added_movie = fireEvent('movie.add', params = {'identifier': imdb_id}, force_readd = False, search_after = False, update_library = True, single = True)
added_movie = fireEvent('movie.add', params = {'identifier': imdb_id}, force_readd = False, search_after = False, update_after = True, single = True)
if added_movie:
movie_ids.append(added_movie['id'])
movie_ids.append(added_movie['_id'])
Env.prop(prop_name, True)
for movie_id in movie_ids:

7
couchpotato/core/plugins/base.py

@ -43,8 +43,8 @@ class Plugin(object):
http_failed_disabled = {}
http_opener = requests.Session()
def __new__(typ, *args, **kwargs):
new_plugin = super(Plugin, typ).__new__(typ)
def __new__(cls, *args, **kwargs):
new_plugin = super(Plugin, cls).__new__(cls)
new_plugin.registerPlugin()
return new_plugin
@ -329,7 +329,7 @@ class Plugin(object):
elif data.get('password'):
tag += '{{%s}}' % data.get('password')
max_length = 127 - len(tag) # Some filesystems don't support 128+ long filenames
max_length = 127 - len(tag) # Some filesystems don't support 128+ long filenames
return '%s%s' % (toSafeString(toUnicode(release_name)[:max_length]), tag)
def createFileName(self, data, filedata, media):
@ -349,6 +349,7 @@ class Plugin(object):
now = time.time()
file_too_new = False
file_time = []
for cur_file in files:
# File got removed while checking

3
couchpotato/core/plugins/browser.py

@ -17,7 +17,8 @@ if os.name == 'nt':
raise ImportError("Missing the win32file module, which is a part of the prerequisite \
pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/")
else:
import win32file #@UnresolvedImport
# noinspection PyUnresolvedReferences
import win32file
autoload = 'FileBrowser'

6
couchpotato/core/plugins/category/static/category.css

@ -22,7 +22,7 @@
.category > .delete:hover {
opacity: 1;
}
.category .ctrlHolder:hover {
background: none;
}
@ -69,7 +69,7 @@
}
#category_ordering li .handle {
background: url('../../static/profile_plugin/handle.png') center;
background: url('../../images/handle.png') center;
width: 20px;
float: right;
}
@ -79,4 +79,4 @@
float: right;
width: 250px;
margin: 0;
}
}

26
couchpotato/core/plugins/category/static/category.js

@ -9,7 +9,7 @@ var CategoryListBase = new Class({
setup: function(categories){
var self = this;
self.categories = []
self.categories = [];
Array.each(categories, self.createCategory.bind(self));
},
@ -17,7 +17,7 @@ var CategoryListBase = new Class({
addSettings: function(){
var self = this;
self.settings = App.getPage('Settings')
self.settings = App.getPage('Settings');
self.settings.addEvent('create', function(){
var tab = self.settings.createSubTab('category', {
'label': 'Categories',
@ -31,7 +31,7 @@ var CategoryListBase = new Class({
self.createList();
self.createOrdering();
})
});
// Add categories in renamer
self.settings.addEvent('create', function(){
@ -97,9 +97,9 @@ var CategoryListBase = new Class({
createCategory: function(data){
var self = this;
var data = data || {'id': randomString()}
var category = new Category(data)
self.categories.include(category)
var data = data || {'id': randomString()};
var category = new Category(data);
self.categories.include(category);
return category;
},
@ -108,7 +108,7 @@ var CategoryListBase = new Class({
var self = this;
var category_list;
var group = self.settings.createGroup({
self.settings.createGroup({
'label': 'Category ordering'
}).adopt(
new Element('.ctrlHolder#category_ordering').adopt(
@ -118,7 +118,7 @@ var CategoryListBase = new Class({
'html': 'Change the order the categories are in the dropdown list.<br />First one will be default.'
})
)
).inject(self.content)
).inject(self.content);
Array.each(self.categories, function(category){
new Element('li', {'data-id': category.data._id}).adopt(
@ -145,7 +145,7 @@ var CategoryListBase = new Class({
var ids = [];
self.category_sortable.list.getElements('li').each(function(el, nr){
self.category_sortable.list.getElements('li').each(function(el){
ids.include(el.get('data-id'));
});
@ -157,7 +157,7 @@ var CategoryListBase = new Class({
}
})
});
window.CategoryList = new CategoryListBase();
@ -235,8 +235,6 @@ var Category = new Class({
if(self.save_timer) clearTimeout(self.save_timer);
self.save_timer = (function(){
var data = self.getData();
Api.request('category.save', {
'data': self.getData(),
'useSpinner': true,
@ -257,7 +255,7 @@ var Category = new Class({
getData: function(){
var self = this;
var data = {
return {
'id' : self.data._id,
'label' : self.el.getElement('.category_label input').get('value'),
'required' : self.el.getElement('.category_required input').get('value'),
@ -265,8 +263,6 @@ var Category = new Class({
'ignored' : self.el.getElement('.category_ignored input').get('value'),
'destination': self.data.destination
}
return data
},
del: function(){

4
couchpotato/core/plugins/dashboard.py

@ -77,8 +77,8 @@ class Dashboard(Plugin):
if coming_soon:
# Don't list older movies
if ((not late and (media['info']['year'] >= now_year-1) and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or
(late and ((media['info']['year'] < now_year-1) or ((eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))))):
if ((not late and (media['info']['year'] >= now_year - 1) and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or
(late and (media['info']['year'] < now_year - 1 or (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200)))):
medias.append(media)
if len(medias) >= limit:

3
couchpotato/core/plugins/file.py

@ -1,5 +1,4 @@
import os.path
import time
import traceback
from couchpotato import get_db
@ -64,7 +63,7 @@ class FileManager(Plugin):
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = None):
if not urlopen_kwargs: urlopen_kwargs = {}
if not dest: # to Cache
if not dest: # to Cache
dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url)))
if not overwrite and os.path.isfile(dest):

6
couchpotato/core/plugins/log/main.py

@ -67,14 +67,14 @@ class Logging(Plugin):
if x is nr:
current_path = path
log = ''
log_content = ''
if current_path:
f = open(current_path, 'r')
log = f.read()
log_content = f.read()
return {
'success': True,
'log': toUnicode(log),
'log': toUnicode(log_content),
'total': total,
}

2
couchpotato/core/plugins/log/static/log.css

@ -50,7 +50,7 @@
overflow: hidden;
line-height: 150%;
font-size: 11px;
font-family: Lucida Console, Monaco, Nimbus Mono L;
font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif;
}
.page.log .container .error {

7
couchpotato/core/plugins/log/static/log.js

@ -42,7 +42,7 @@ Page.Log = new Class({
}
}
}).inject(nav);
};
}
new Element('li', {
'text': 'clear',
@ -63,7 +63,6 @@ Page.Log = new Class({
},
addColors: function(text){
var self = this;
text = text
.replace(/&/g, '&amp;')
@ -74,9 +73,9 @@ Page.Log = new Class({
.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>')
.replace(/\u001b\[0m/gi, '</span><span>');
return '<div class="time">' + text + '</div>';
}
})
});

8
couchpotato/core/plugins/manage.py

@ -160,7 +160,7 @@ class Manage(Plugin):
except:
log.error('Failed updating library: %s', (traceback.format_exc()))
while True and not self.shuttingDown():
while self.in_progress and len(self.in_progress) > 0 and not self.shuttingDown():
delete_me = {}
@ -171,14 +171,12 @@ class Manage(Plugin):
for delete in delete_me:
del self.in_progress[delete]
if len(self.in_progress) == 0:
break
time.sleep(1)
fireEvent('notify.frontend', type = 'manage.updating', data = False)
self.in_progress = False
# noinspection PyDefaultArgument
def createAddToLibrary(self, folder, added_identifiers = []):
def addToLibrary(group, total_found, to_go):
@ -219,7 +217,7 @@ class Manage(Plugin):
pr = self.in_progress[folder]
pr['to_go'] -= 1
avg = (time.time() - pr['started'])/(pr['total'] - pr['to_go'])
avg = (time.time() - pr['started']) / (pr['total'] - pr['to_go'])
pr['eta'] = tryInt(avg * pr['to_go'])

17
couchpotato/core/plugins/profile/main.py

@ -79,7 +79,8 @@ class ProfilePlugin(Plugin):
'core': kwargs.get('core', False),
'qualities': [],
'wait_for': [],
'finish': []
'finish': [],
'3d': []
}
# Update types
@ -88,6 +89,7 @@ class ProfilePlugin(Plugin):
profile['qualities'].append(type.get('quality'))
profile['wait_for'].append(tryInt(type.get('wait_for')))
profile['finish'].append((tryInt(type.get('finish')) == 1) if order > 0 else True)
profile['3d'].append(tryInt(type.get('3d')))
order += 1
id = kwargs.get('id')
@ -184,6 +186,14 @@ class ProfilePlugin(Plugin):
}, {
'label': 'SD',
'qualities': ['dvdrip', 'dvdr']
}, {
'label': 'Prefer 3D HD',
'qualities': ['1080p', '720p', '720p', '1080p'],
'3d': [True, True]
}, {
'label': '3D HD',
'qualities': ['1080p', '720p'],
'3d': [True, True]
}]
# Create default quality profile
@ -197,12 +207,15 @@ class ProfilePlugin(Plugin):
'order': order,
'qualities': profile.get('qualities'),
'finish': [],
'wait_for': []
'wait_for': [],
'3d': []
}
threed = profile.get('3d', [])
for q in profile.get('qualities'):
pro['finish'].append(True)
pro['wait_for'].append(0)
pro['3d'].append(threed.pop() if threed else False)
db.insert(pro)
order += 1

54
couchpotato/core/plugins/profile/static/profile.css

@ -22,18 +22,17 @@
.profile > .delete:hover {
opacity: 1;
}
.profile .ctrlHolder:hover {
background: none;
}
.profile .qualities {
min-height: 80px;
padding-top: 0;
}
.profile .formHint {
width: 250px !important;
width: 210px !important;
vertical-align: top !important;
margin: 0 !important;
padding-left: 3px !important;
@ -78,21 +77,55 @@
}
.profile .quality_type select {
width: 186px;
width: 120px;
margin-left: -1px;
}
.profile .types li.is_empty .check, .profile .types li.is_empty .delete, .profile .types li.is_empty .handle {
.profile .types li.is_empty .check,
.profile .types li.is_empty .delete,
.profile .types li.is_empty .handle,
.profile .types li.is_empty .check_label {
visibility: hidden;
}
.profile .types .type label {
display: inline-block;
width: auto;
float: none;
text-transform: uppercase;
font-size: 11px;
font-weight: normal;
margin-right: 20px;
text-shadow: none;
vertical-align: bottom;
padding: 0;
height: 17px;
}
.profile .types .type label .check {
margin-right: 5px;
}
.profile .types .type label .check_label {
display: inline-block;
vertical-align: top;
height: 16px;
line-height: 13px;
}
.profile .types .type .threed {
display: none;
}
.profile .types .type.allow_3d .threed {
display: inline-block;
}
.profile .types .type .handle {
background: url('../../static/profile_plugin/handle.png') center;
background: url('../../images/handle.png') center;
display: inline-block;
height: 20px;
width: 20px;
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: -webkit-grab;
cursor: grab;
margin: 0;
}
@ -106,6 +139,9 @@
font-size: 13px;
color: #fd5353;
}
.profile .types .type:not(.allow_3d) .delete {
margin-left: 55px;
}
.profile .types .type:hover:not(.is_empty) .delete {
visibility: visible;
@ -144,7 +180,7 @@
}
#profile_ordering li .handle {
background: url('../../static/profile_plugin/handle.png') center;
background: url('../../images/handle.png') center;
width: 20px;
float: right;
}
@ -154,4 +190,4 @@
float: right;
width: 250px;
margin: 0;
}
}

67
couchpotato/core/plugins/profile/static/profile.js

@ -63,6 +63,7 @@ var Profile = new Class({
data.types.include({
'quality': quality,
'finish': data.finish[nr] || false,
'3d': data['3d'] ? data['3d'][nr] || false : false,
'wait_for': data.wait_for[nr] || 0
})
});
@ -99,7 +100,7 @@ var Profile = new Class({
'onComplete': function(json){
if(json.success){
self.data = json.profile;
self.type_container.getElement('li:first-child input[type=checkbox]')
self.type_container.getElement('li:first-child input.finish[type=checkbox]')
.set('checked', true)
.getParent().addClass('checked');
}
@ -118,16 +119,17 @@ var Profile = new Class({
'label' : self.el.getElement('.quality_label input').get('value'),
'wait_for' : self.el.getElement('.wait_for input').get('value'),
'types': []
}
};
Array.each(self.type_container.getElements('.type'), function(type){
if(!type.hasClass('deleted') && type.getElement('select').get('value') != -1)
data.types.include({
'quality': type.getElement('select').get('value'),
'finish': +type.getElement('input[type=checkbox]').checked,
'finish': +type.getElement('input.finish[type=checkbox]').checked,
'3d': +type.getElement('input.3d[type=checkbox]').checked,
'wait_for': 0
});
})
});
return data
},
@ -240,6 +242,7 @@ Profile.Type = new Class({
self.addEvent('change', function(){
self.el[self.qualities.get('value') == '-1' ? 'addClass' : 'removeClass']('is_empty');
self.el[Quality.getQuality(self.qualities.get('value')).allow_3d ? 'addClass': 'removeClass']('allow_3d');
self.deleted = self.qualities.get('value') == '-1';
});
@ -250,24 +253,40 @@ Profile.Type = new Class({
var data = self.data;
self.el = new Element('li.type').adopt(
new Element('span.quality_type').adopt(
new Element('span.quality_type').grab(
self.fillQualities()
),
new Element('span.finish').adopt(
self.finish = new Element('input.inlay.finish[type=checkbox]', {
'checked': data.finish !== undefined ? data.finish : 1,
'events': {
'change': function(e){
if(self.el == self.el.getParent().getElement(':first-child')){
self.finish_class.check();
alert('Top quality always finishes the search')
return;
self.finish_container = new Element('label.finish').adopt(
new Element('span.finish').grab(
self.finish = new Element('input.inlay.finish[type=checkbox]', {
'checked': data.finish !== undefined ? data.finish : 1,
'events': {
'change': function(){
if(self.el == self.el.getParent().getElement(':first-child')){
self.finish_class.check();
alert('Top quality always finishes the search');
return;
}
self.fireEvent('change');
}
self.fireEvent('change');
}
}
})
})
),
new Element('span.check_label[text=finish]')
),
self['3d_container'] = new Element('label.threed').adopt(
new Element('span.3d').grab(
self['3d'] = new Element('input.inlay.3d[type=checkbox]', {
'checked': data['3d'] !== undefined ? data['3d'] : 0,
'events': {
'change': function(){
self.fireEvent('change');
}
}
})
),
new Element('span.check_label[text=3D]')
),
new Element('span.delete.icon2', {
'events': {
@ -279,7 +298,11 @@ Profile.Type = new Class({
self.el[self.data.quality ? 'removeClass' : 'addClass']('is_empty');
if(self.data.quality && Quality.getQuality(self.data.quality).allow_3d)
self.el.addClass('allow_3d');
self.finish_class = new Form.Check(self.finish);
self['3d_class'] = new Form.Check(self['3d']);
},
@ -290,7 +313,7 @@ Profile.Type = new Class({
'events': {
'change': self.fireEvent.bind(self, 'change')
}
}).adopt(
}).grab(
new Element('option', {
'text': '+ Add another quality',
'value': -1
@ -300,7 +323,8 @@ Profile.Type = new Class({
Object.each(Quality.qualities, function(q){
new Element('option', {
'text': q.label,
'value': q.identifier
'value': q.identifier,
'data-allow_3d': q.allow_3d
}).inject(self.qualities)
});
@ -316,6 +340,7 @@ Profile.Type = new Class({
return {
'quality': self.qualities.get('value'),
'finish': +self.finish.checked,
'3d': +self['3d'].checked,
'wait_for': 0
}
},
@ -338,4 +363,4 @@ Profile.Type = new Class({
return this.el;
}
})
});

60
couchpotato/core/plugins/quality/main.py

@ -21,9 +21,9 @@ class QualityPlugin(Plugin):
}
qualities = [
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
{'identifier': '1080p', 'hd': True, 'size': (4000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts', 'x264', 'h264']},
{'identifier': '720p', 'hd': True, 'size': (3000, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts'], 'tags': ['x264', 'h264']},
{'identifier': 'bd50', 'hd': True, 'allow_3d': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
{'identifier': '1080p', 'hd': True, 'allow_3d': True, 'size': (4000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts', 'x264', 'h264']},
{'identifier': '720p', 'hd': True, 'allow_3d': True, 'size': (3000, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts'], 'tags': ['x264', 'h264']},
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':[], 'tags': ['hdtv', 'hdrip', 'webdl', ('web', 'dl')]},
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': ['br2dvd'], 'allow': [], 'ext':['iso', 'img', 'vob'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts', ('dvd', 'r')]},
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': [], 'allow': [], 'ext':[], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
@ -34,6 +34,11 @@ class QualityPlugin(Plugin):
{'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': [], 'ext':[]}
]
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
threed_tags = {
'hsbs': [('half', 'sbs')],
'fsbs': [('full', 'sbs')],
'3d': [],
}
cached_qualities = None
cached_order = None
@ -58,6 +63,7 @@ class QualityPlugin(Plugin):
addEvent('app.test', self.doTest)
self.order = []
self.addOrder()
def addOrder(self):
@ -185,14 +191,19 @@ class QualityPlugin(Plugin):
# Start with 0
score = {}
for quality in qualities:
score[quality.get('identifier')] = 0
score[quality.get('identifier')] = {
'score': 0,
'3d': {}
}
for cur_file in files:
words = re.split('\W+', cur_file.lower())
for quality in qualities:
contains_score = self.containsTagScore(quality, words, cur_file)
self.calcScore(score, quality, contains_score)
threedscore = self.contains3D(quality, words, cur_file) if quality.get('allow_3d') else (0, None)
self.calcScore(score, quality, contains_score, threedscore)
# Try again with loose testing
for quality in qualities:
@ -208,10 +219,13 @@ class QualityPlugin(Plugin):
if not has_non_zero:
return None
heighest_quality = max(score, key = score.get)
heighest_quality = max(score, key = lambda p: score[p]['score'])
if heighest_quality:
for quality in qualities:
if quality.get('identifier') == heighest_quality:
quality['is_3d'] = False
if score[heighest_quality].get('3d'):
quality['is_3d'] = True
return self.setCache(cache_key, quality)
return None
@ -234,12 +248,12 @@ class QualityPlugin(Plugin):
qualities = [qualities] if isinstance(qualities, (str, unicode)) else qualities
for alt in qualities:
if (isinstance(alt, tuple)):
if isinstance(alt, tuple):
if len(set(words) & set(alt)) == len(alt):
log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file))
score += points.get(tag_type)
if (isinstance(alt, (str, unicode)) and ss(alt.lower()) in cur_file.lower()):
if isinstance(alt, (str, unicode)) and ss(alt.lower()) in cur_file.lower():
log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file))
score += points.get(tag_type) / 2
@ -255,6 +269,23 @@ class QualityPlugin(Plugin):
return score
def contains3D(self, quality, words, cur_file = ''):
cur_file = ss(cur_file)
for key in self.threed_tags:
tags = self.threed_tags.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 cur_file.lower()):
log.debug('Found %s in %s', (tag, cur_file))
return 1, key
if list(set([key]) & set(words)):
log.debug('Found %s in %s', (tag, cur_file))
return 1, key
return 0, None
def guessLooseScore(self, quality, extra = None):
score = 0
@ -277,9 +308,16 @@ class QualityPlugin(Plugin):
return score
def calcScore(self, score, quality, add_score):
def calcScore(self, score, quality, add_score, threedscore = (0, None)):
score[quality['identifier']]['score'] += add_score
threedscore, threedtag = threedscore
if threedscore and threedtag:
if threedscore not in score[quality['identifier']]['3d']:
score[quality['identifier']]['3d'][threedtag] = 0
score[quality['identifier']] += add_score
score[quality['identifier']]['3d'][threedtag] += threedscore
# Set order for allow calculation (and cache)
if not self.cached_order:
@ -289,7 +327,7 @@ class QualityPlugin(Plugin):
if add_score != 0:
for allow in quality.get('allow', []):
score[allow] -= 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
def doTest(self):

18
couchpotato/core/plugins/quality/static/quality.js

@ -8,7 +8,7 @@ var QualityBase = new Class({
self.qualities = data.qualities;
self.profiles = []
self.profiles = [];
Array.each(data.profiles, self.createProfilesClass.bind(self));
App.addEvent('load', self.addSettings.bind(self))
@ -37,7 +37,7 @@ var QualityBase = new Class({
addSettings: function(){
var self = this;
self.settings = App.getPage('Settings')
self.settings = App.getPage('Settings');
self.settings.addEvent('create', function(){
var tab = self.settings.createSubTab('profile', {
'label': 'Quality',
@ -91,9 +91,9 @@ var QualityBase = new Class({
createProfilesClass: function(data){
var self = this;
var data = data || {'id': randomString()}
var profile = new Profile(data)
self.profiles.include(profile)
var data = data || {'id': randomString()};
var profile = new Profile(data);
self.profiles.include(profile);
return profile;
},
@ -102,7 +102,7 @@ var QualityBase = new Class({
var self = this;
var profile_list;
var group = self.settings.createGroup({
self.settings.createGroup({
'label': 'Profile Defaults',
'description': '(Needs refresh \'' +(App.isMac() ? 'CMD+R' : 'F5')+ '\' after editing)'
}).adopt(
@ -113,7 +113,7 @@ var QualityBase = new Class({
'html': 'Change the order the profiles are in the dropdown list. Uncheck to hide it completely.<br />First one will be default.'
})
)
).inject(self.content)
).inject(self.content);
Array.each(self.profiles, function(profile){
var check;
@ -175,14 +175,14 @@ var QualityBase = new Class({
'description': 'Edit the minimal and maximum sizes (in MB) for each quality.',
'advanced': true,
'name': 'sizes'
}).inject(self.content)
}).inject(self.content);
new Element('div.item.head.ctrlHolder').adopt(
new Element('span.label', {'text': 'Quality'}),
new Element('span.min', {'text': 'Min'}),
new Element('span.max', {'text': 'Max'})
).inject(group)
).inject(group);
Array.each(self.qualities, function(quality){
new Element('div.ctrlHolder.item').adopt(

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save