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

1
couchpotato/__init__.py

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

2
couchpotato/core/_base/_core.py

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

3
couchpotato/core/_base/clientscript.py

@ -57,7 +57,6 @@ class ClientScript(Plugin):
], ],
} }
urls = {'style': {}, 'script': {}} urls = {'style': {}, 'script': {}}
minified = {'style': {}, 'script': {}} minified = {'style': {}, 'script': {}}
paths = {'style': {}, 'script': {}} paths = {'style': {}, 'script': {}}
@ -95,7 +94,6 @@ class ClientScript(Plugin):
else: else:
self.registerStyle(core_url, file_path, position = 'front') self.registerStyle(core_url, file_path, position = 'front')
def minify(self): def minify(self):
# Create cache dir # Create cache dir
@ -192,6 +190,7 @@ class ClientScript(Plugin):
prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow'] prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow']
prefix_tags = ['ms', 'moz', 'webkit'] prefix_tags = ['ms', 'moz', 'webkit']
def prefix(self, data): def prefix(self, data):
trimmed_data = re.sub('(\t|\n|\r)+', '', 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): def info(self):
version = self.getVersion() current_version = self.getVersion()
return { return {
'last_check': self.last_check, 'last_check': self.last_check,
'update_version': self.update_version, 'update_version': self.update_version,
'version': version, 'version': current_version,
'repo_name': '%s/%s' % (self.repo_user, self.repo_name), '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): def getVersion(self):

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

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

39
couchpotato/core/database.py

@ -21,6 +21,8 @@ class Database(object):
def __init__(self): def __init__(self):
addApiView('database.list_documents', self.listDocuments) 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.update', self.updateDocument)
addApiView('database.document.delete', self.deleteDocument) addApiView('database.document.delete', self.deleteDocument)
@ -114,9 +116,36 @@ class Database(object):
results[key] = [] results[key] = []
results[key].append(document) results[key].append(document)
return results 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): def migrate(self):
from couchpotato import Env 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]] = [migrate_data[ml][p[0]]]
migrate_data[ml][p[0]].append(columns) migrate_data[ml][p[0]].append(columns)
c.close() conn.close()
log.info('Getting data took %s', time.time() - migrate_start) log.info('Getting data took %s', time.time() - migrate_start)
@ -297,6 +326,11 @@ class Database(object):
releaseinfos = migrate_data['releaseinfo'] releaseinfos = migrate_data['releaseinfo']
for x in releaseinfos: for x in releaseinfos:
info = releaseinfos[x] 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'): if not migrate_data['release'][info.get('release_id')].get('info'):
migrate_data['release'][info.get('release_id')]['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('Total migration took %s', time.time() - migrate_start)
log.info('=' * 30) log.info('=' * 30)
# rename old database # rename old database
log.info('Renaming old database to %s ', old_db + '.old') log.info('Renaming old database to %s ', old_db + '.old')
os.rename(old_db, 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 = [ torrent_trackers = [
'http://tracker.publicbt.com/announce',
'udp://tracker.istole.it:80/announce', 'udp://tracker.istole.it:80/announce',
'udp://fr33domtracker.h33t.com:3310/announce',
'http://tracker.istole.it/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', 'udp://tracker.publicbt.com:80/announce',
'http://tracker.ccc.de/announce',
'udp://tracker.ccc.de:80/announce', 'udp://tracker.ccc.de:80/announce',
'http://exodus.desync.com/announce', 'http://exodus.desync.com/announce',
'http://exodus.desync.com:6969/announce', 'http://exodus.desync.com:6969/announce',
'http://tracker.publichd.eu/announce', 'http://tracker.publichd.eu/announce',
'udp://tracker.publichd.eu:80/announce',
'http://tracker.openbittorrent.com/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): def __init__(self):
@ -184,6 +188,7 @@ class Downloader(Provider):
def pause(self, release_download, pause): def pause(self, release_download, pause):
return return
class ReleaseDownloadList(list): class ReleaseDownloadList(list):
provider = None provider = None

24
couchpotato/core/downloaders/qbittorrent_.py

@ -14,6 +14,7 @@ log = CPLog(__name__)
autoload = 'qBittorrent' autoload = 'qBittorrent'
class qBittorrent(Downloader): class qBittorrent(Downloader):
protocol = ['torrent', 'torrent_magnet'] protocol = ['torrent', 'torrent_magnet']
@ -107,25 +108,24 @@ class qBittorrent(Downloader):
for torrent in torrents: for torrent in torrents:
if torrent.hash in ids: if torrent.hash in ids:
torrent.update_general() # get extra info torrent.update_general() # get extra info
torrent_filelist = torrent.get_files()
torrent_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(torrent_dir):
if os.path.isdir(check_dir): torrent.save_path = torrent_dir
torrent.save_path = check_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 root, _, files in os.walk(torrent.save_path):
for f in files: for f in files:
p = os.path.join(root, f) torrent_files.append(sp(os.path.join(root, f)))
if os.path.isfile(p):
torrent_files.append(sp(p))
else: # multi or single file placed directly in torrent.save_path else: # multi or single file placed directly in torrent.save_path
for f in t_files: for f in torrent_filelist:
p = os.path.join(torrent.save_path, f.name) file_path = os.path.join(torrent.save_path, f.name)
if os.path.isfile(p): if os.path.isfile(file_path):
torrent_files.append(sp(p)) torrent_files.append(sp(file_path))
release_downloads.append({ release_downloads.append({
'id': torrent.hash, 'id': torrent.hash,

45
couchpotato/core/downloaders/rtorrent_.py

@ -94,44 +94,6 @@ class rTorrent(Downloader):
return False 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): def download(self, data = None, media = None, filedata = None):
if not media: media = {} if not media: media = {}
@ -142,10 +104,6 @@ class rTorrent(Downloader):
if not self.connect(): if not self.connect():
return False return False
group_name = 'cp_' + data.get('provider').lower()
if not self.updateProviderGroup(group_name, data):
return False
torrent_params = {} torrent_params = {}
if self.conf('label'): if self.conf('label'):
torrent_params['label'] = self.conf('label') torrent_params['label'] = self.conf('label')
@ -186,9 +144,6 @@ class rTorrent(Downloader):
if self.conf('directory'): if self.conf('directory'):
torrent.set_directory(self.conf('directory')) torrent.set_directory(self.conf('directory'))
# Set Ratio Group
torrent.set_visible(group_name)
# Start torrent # Start torrent
if not self.conf('paused', default = 0): if not self.conf('paused', default = 0):
torrent.start() 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 '')) 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) return self.trpc.remove_torrent(release_download['id'], delete_files)
class TransmissionRPC(object): class TransmissionRPC(object):
"""TransmissionRPC lite library""" """TransmissionRPC lite library"""

16
couchpotato/core/downloaders/utorrent.py

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

3
couchpotato/core/helpers/request.py

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

7
couchpotato/core/helpers/variable.py

@ -216,6 +216,7 @@ def tryFloat(s):
return float(s) return float(s)
except: return 0 except: return 0
def natsortKey(string_): def natsortKey(string_):
"""See http://www.codinghorror.com/blog/archives/001018.html""" """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_)] 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: except:
log.error('Could not get title for %s', getIdentifier(media_dict)) log.error('Could not get title for %s', getIdentifier(media_dict))
return None return None
log.error('Could not get title for %s', getIdentifier(media_dict))
return None
except: except:
log.error('Could not get title for library item: %s', media_dict) log.error('Could not get title for library item: %s', media_dict)
return None 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 # 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) 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 # From SABNZBD
re_password = [re.compile(r'(.+){{([^{}]+)}}$'), re.compile(r'(.+)\s+password\s*=\s*(.+)$', re.I)] re_password = [re.compile(r'(.+){{([^{}]+)}}$'), re.compile(r'(.+)\s+password\s*=\s*(.+)$', re.I)]
def scanForPassword(name): def scanForPassword(name):
m = None m = None
for reg in re_password: for reg in re_password:

13
couchpotato/core/loader.py

@ -12,19 +12,22 @@ log = CPLog(__name__)
class Loader(object): class Loader(object):
plugins = {}
providers = {} def __init__(self):
modules = {} self.plugins = {}
self.providers = {}
self.modules = {}
self.paths = {}
def preload(self, root = ''): def preload(self, root = ''):
core = os.path.join(root, 'couchpotato', 'core') core = os.path.join(root, 'couchpotato', 'core')
self.paths = { self.paths.update({
'core': (0, 'couchpotato.core._base', os.path.join(core, '_base')), 'core': (0, 'couchpotato.core._base', os.path.join(core, '_base')),
'plugin': (1, 'couchpotato.core.plugins', os.path.join(core, 'plugins')), 'plugin': (1, 'couchpotato.core.plugins', os.path.join(core, 'plugins')),
'notifications': (20, 'couchpotato.core.notifications', os.path.join(core, 'notifications')), 'notifications': (20, 'couchpotato.core.notifications', os.path.join(core, 'notifications')),
'downloaders': (20, 'couchpotato.core.downloaders', os.path.join(core, 'downloaders')), 'downloaders': (20, 'couchpotato.core.downloaders', os.path.join(core, 'downloaders')),
} })
# Add media to loader # Add media to loader
self.addPath(root, ['couchpotato', 'core', 'media'], 25, recursive = True) 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.event import addEvent, fireEvent, fireEventAsync
from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
import six
log = CPLog(__name__) log = CPLog(__name__)

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

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

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

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

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

@ -99,7 +99,7 @@ from couchpotato.core.helpers.encoding import simplifyString"""
class TitleIndex(TreeBasedIndex): class TitleIndex(TreeBasedIndex):
_version = 1 _version = 2
custom_header = """from CodernityDB.tree_index import TreeBasedIndex custom_header = """from CodernityDB.tree_index import TreeBasedIndex
from string import ascii_letters from string import ascii_letters
@ -113,14 +113,14 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
return self.simplify(key) return self.simplify(key)
def make_key_value(self, data): 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 return self.simplify(data['title']), None
def simplify(self, title): def simplify(self, title):
title = toUnicode(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) title = simplifyString(title)
for prefix in ['the ']: for prefix in ['the ']:
@ -132,7 +132,7 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
class StartsWithIndex(TreeBasedIndex): class StartsWithIndex(TreeBasedIndex):
_version = 1 _version = 2
custom_header = """from CodernityDB.tree_index import TreeBasedIndex custom_header = """from CodernityDB.tree_index import TreeBasedIndex
from string import ascii_letters from string import ascii_letters
@ -158,4 +158,4 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
title = title[len(prefix):] title = title[len(prefix):]
break 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: if media:
# Attach category # Attach category
if media.get('category_id'): try: media['category'] = db.get('id', media.get('category_id'))
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 return media
@ -222,7 +222,6 @@ class MediaPlugin(MediaBase):
del filter_by['media_status'] del filter_by['media_status']
del filter_by['release_status'] del filter_by['release_status']
# Filter by combining ids # Filter by combining ids
for x in filter_by: for x in filter_by:
media_ids = [n for n in media_ids if n in filter_by[x]] 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): for media_type in fireEvent('media.types', merge = True):
def tempList(*args, **kwargs): 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) addApiView('%s.list' % media_type, tempList)
def availableChars(self, types = None, status = None, release_status = None): 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): for media_type in fireEvent('media.types', merge = True):
def tempChar(*args, **kwargs): 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) addApiView('%s.available_chars' % media_type, tempChar)
def delete(self, media_id, delete_from = None): def delete(self, media_id, delete_from = None):
@ -366,7 +365,7 @@ class MediaPlugin(MediaBase):
deleted = True deleted = True
else: 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_releases = len(media_releases)
total_deleted = 0 total_deleted = 0
@ -382,9 +381,8 @@ class MediaPlugin(MediaBase):
if release.get('status') == 'done': if release.get('status') == 'done':
db.delete(release) db.delete(release)
total_deleted += 1 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) db.delete(media)
deleted = True deleted = True
elif new_media_status: elif new_media_status:
@ -432,7 +430,7 @@ class MediaPlugin(MediaBase):
move_to_wanted = True move_to_wanted = True
profile = db.get('id', m['profile_id']) 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']: for q_identifier in profile['qualities']:
index = profile['qualities'].index(q_identifier) index = profile['qualities'].index(q_identifier)

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

@ -244,10 +244,16 @@ class YarrProvider(Provider):
return 0 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: for ids, qualities in self.cat_ids:
if identifier in qualities: if identifier in qualities or (want_3d and '3d' in qualities):
return ids return ids
if self.cat_backup_id: if self.cat_backup_id:

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

@ -28,7 +28,7 @@ class Base(NZBProvider):
try: try:
html = BeautifulSoup(data) html = BeautifulSoup(data)
main_table = html.find('table', attrs = {'id':'r2'}) main_table = html.find('table', attrs = {'id': 'r2'})
if not main_table: if not main_table:
return return
@ -36,11 +36,11 @@ class Base(NZBProvider):
items = main_table.find_all('tr') items = main_table.find_all('tr')
for row in items: for row in items:
title = row.find('span', attrs = {'class':'s'}) title = row.find('span', attrs = {'class': 's'})
if not title: continue 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'}) info = row.find('span', attrs = {'class':'d'})
size_match = re.search('size:.(?P<size>[0-9\.]+.[GMB]+)', info.text) 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', '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): def _search(self, media, quality, results):
@ -55,7 +55,7 @@ class Base(NZBProvider, RSS):
def getMoreInfo(self, item): def getMoreInfo(self, item):
full_description = self.getCache('nzbclub.%s' % item['id'], item['detail_url'], cache_timeout = 25920000) full_description = self.getCache('nzbclub.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
html = BeautifulSoup(full_description) 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 '' description = toUnicode(nfo_pre.text) if nfo_pre else ''
item['description'] = description 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') 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) full_description = self.getCache('nzbindex.%s' % item['id'], url = nfo_url, cache_timeout = 25920000)
html = BeautifulSoup(full_description) 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: except:
pass 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', 'detail_url': 'https://omgwtfnzbs.org/details.php?id=%s',
} }
http_time_between_calls = 1 #seconds http_time_between_calls = 1 # Seconds
cat_ids = [ cat_ids = [
([15], ['dvdrip']), ([15], ['dvdrip']),
@ -42,7 +42,7 @@ class Base(NZBProvider, RSS):
q = '%s %s' % (title, movie['info']['year']) q = '%s %s' % (title, movie['info']['year'])
params = tryUrlencode({ params = tryUrlencode({
'search': q, '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 = ''), 'user': self.conf('username', default = ''),
'api': self.conf('api_key', 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 # 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): def _search(self, media, quality, results):
@ -40,7 +40,7 @@ class Base(TorrentProvider):
html = BeautifulSoup(data) html = BeautifulSoup(data)
try: try:
result_table = html.find('table', attrs = {'width' : '750', 'class' : ''}) result_table = html.find('table', attrs = {'width': '750', 'class': ''})
if result_table is None: if result_table is None:
return return
@ -74,7 +74,7 @@ class Base(TorrentProvider):
def getMoreInfo(self, item): def getMoreInfo(self, item):
full_description = self.getCache('bithdtv.%s' % item['id'], item['detail_url'], cache_timeout = 25920000) full_description = self.getCache('bithdtv.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
html = BeautifulSoup(full_description) 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 '' description = toUnicode(nfo_pre.text) if nfo_pre else ''
item['description'] = description 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', '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): def _searchOnTitle(self, title, movie, quality, results):
@ -30,7 +30,7 @@ class Base(TorrentProvider):
}) })
url = "%s&%s" % (self.urls['search'], arguments) 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) data = self.getHTMLData(url)
if data: if data:
@ -74,7 +74,6 @@ class Base(TorrentProvider):
except: except:
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
def getLoginParams(self): def getLoginParams(self):
return { return {
'username': self.conf('username'), 'username': self.conf('username'),
@ -82,7 +81,6 @@ class Base(TorrentProvider):
'ssl': 'yes', 'ssl': 'yes',
} }
def loginSuccess(self, output): def loginSuccess(self, output):
return 'logout.php' in output.lower() 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' 'api': 'https://hdbits.org/api/torrents'
} }
http_time_between_calls = 1 #seconds http_time_between_calls = 1 # Seconds
def _post_query(self, **params): def _post_query(self, **params):
@ -56,7 +56,7 @@ class Base(TorrentProvider):
'name': result['name'], 'name': result['name'],
'url': self.urls['download'] % (result['id'], self.conf('passkey')), 'url': self.urls['download'] % (result['id'], self.conf('passkey')),
'detail_url': self.urls['detail'] % result['id'], 'detail_url': self.urls['detail'] % result['id'],
'size': tryInt(result['size'])/1024/1024, 'size': tryInt(result['size']) / 1024 / 1024,
'seeders': tryInt(result['seeders']), 'seeders': tryInt(result['seeders']),
'leechers': tryInt(result['leechers']) 'leechers': tryInt(result['leechers'])
}) })

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

@ -36,7 +36,7 @@ class Base(TorrentProvider):
page = 0 page = 0
total_pages = 1 total_pages = 1
cats = self.getCatId(quality['identifier']) cats = self.getCatId(quality)
while page < total_pages: while page < total_pages:
@ -79,7 +79,7 @@ class Base(TorrentProvider):
return confirmed + trusted + vip + moderated return confirmed + trusted + vip + moderated
id = re.search('id=(?P<id>\d+)&', link).group('id') 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) fileSize = self.parseSize(result.select('td.rowhead')[5].text)
results.append({ results.append({
@ -113,7 +113,7 @@ class Base(TorrentProvider):
try: try:
full_description = self.getHTMLData(item['detail_url']) full_description = self.getHTMLData(item['detail_url'])
html = BeautifulSoup(full_description) 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 '' description = toUnicode(nfo_pre.text) if nfo_pre else ''
except: except:
log.error('Failed getting more info for %s', item['name']) 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', 'base_url': 'https://www.iptorrents.com',
'login': 'https://www.iptorrents.com/torrents/', 'login': 'https://www.iptorrents.com/torrents/',
'login_check': 'https://www.iptorrents.com/inbox.php', '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 cat_backup_id = None
def buildUrl(self, title, media, quality): 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: 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 None
return self.urls['search'] % ("&".join(("l%d=" % x) for x in cat_ids), tryUrlencode(query).replace('%', '%%')) 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) html = BeautifulSoup(data)
try: try:
page_nav = html.find('span', attrs = {'class' : 'page_nav'}) page_nav = html.find('span', attrs = {'class': 'page_nav'})
if page_nav: if page_nav:
next_link = page_nav.find("a", text = "Next") next_link = page_nav.find("a", text = "Next")
if next_link: if next_link:
final_page_link = next_link.previous_sibling.previous_sibling final_page_link = next_link.previous_sibling.previous_sibling
pages = int(final_page_link.string) 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(): if not result_table or 'nothing found!' in data.lower():
return 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_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_details_url = self.urls['base_url'] + torrent['href']
torrent_size = self.parseSize(result.find_all('td')[5].string) torrent_size = self.parseSize(result.find_all('td')[5].string)
torrent_seeders = tryInt(result.find('td', attrs = {'class' : 'ac t_seeders'}).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_leechers = tryInt(result.find('td', attrs = {'class': 'ac t_leechers'}).string)
results.append({ results.append({
'id': torrent_id, 'id': torrent_id,

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

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

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

@ -136,23 +136,23 @@ class Base(TorrentProvider):
def htmlToUnicode(self, text): def htmlToUnicode(self, text):
def fixup(m): def fixup(m):
text = m.group(0) txt = m.group(0)
if text[:2] == "&#": if txt[:2] == "&#":
# character reference # character reference
try: try:
if text[:3] == "&#x": if txt[:3] == "&#x":
return unichr(int(text[3:-1], 16)) return unichr(int(txt[3:-1], 16))
else: else:
return unichr(int(text[2:-1])) return unichr(int(txt[2:-1]))
except ValueError: except ValueError:
pass pass
else: else:
# named entity # named entity
try: try:
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]) txt = unichr(htmlentitydefs.name2codepoint[txt[1:-1]])
except KeyError: except KeyError:
pass pass
return text # leave as is return txt # leave as is
return re.sub("&#?\w+;", fixup, six.u('%s') % text) return re.sub("&#?\w+;", fixup, six.u('%s') % text)
def unicodeToASCII(self, text): def unicodeToASCII(self, text):

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

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

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

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

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

@ -39,7 +39,7 @@ class Base(TorrentMagnetProvider):
page = 0 page = 0
total_pages = 1 total_pages = 1
cats = self.getCatId(quality['identifier']) cats = self.getCatId(quality)
base_search_url = self.urls['search'] % self.getDomain() base_search_url = self.urls['search'] % self.getDomain()
@ -108,7 +108,7 @@ class Base(TorrentMagnetProvider):
def getMoreInfo(self, item): def getMoreInfo(self, item):
full_description = self.getCache('tpb.%s' % item['id'], item['detail_url'], cache_timeout = 25920000) full_description = self.getCache('tpb.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
html = BeautifulSoup(full_description) html = BeautifulSoup(full_description)
nfo_pre = html.find('div', attrs = {'class':'nfo'}) nfo_pre = html.find('div', attrs = {'class': 'nfo'})
description = toUnicode(nfo_pre.text) if nfo_pre else '' description = toUnicode(nfo_pre.text) if nfo_pre else ''
item['description'] = description item['description'] = description

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

@ -31,19 +31,19 @@ class Base(TorrentProvider):
([20], ['dvdr']), ([20], ['dvdr']),
] ]
http_time_between_calls = 1 #seconds http_time_between_calls = 1 # Seconds
cat_backup_id = None cat_backup_id = None
def _searchOnTitle(self, title, movie, quality, results): 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) data = self.getHTMLData(url)
if data: if data:
html = BeautifulSoup(data) html = BeautifulSoup(data)
try: try:
result_table = html.find('table', attrs = {'border' : '1'}) result_table = html.find('table', attrs = {'border': '1'})
if not result_table: if not result_table:
return return
@ -52,7 +52,7 @@ class Base(TorrentProvider):
for result in entries[1:]: for result in entries[1:]:
cells = result.find_all('td') 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=', '') full_id = link['href'].replace('details.php?id=', '')
torrent_id = full_id[:6] 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', '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): 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', 'download': 'http://www.torrentleech.org%s',
} }
http_time_between_calls = 1 #seconds http_time_between_calls = 1 # Seconds
cat_backup_id = None cat_backup_id = None
def _search(self, media, quality, results): def _search(self, media, quality, results):
@ -34,7 +34,7 @@ class Base(TorrentProvider):
html = BeautifulSoup(data) html = BeautifulSoup(data)
try: try:
result_table = html.find('table', attrs = {'id' : 'torrenttable'}) result_table = html.find('table', attrs = {'id': 'torrenttable'})
if not result_table: if not result_table:
return return
@ -42,9 +42,9 @@ class Base(TorrentProvider):
for result in entries[1:]: for result in entries[1:]:
link = 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') url = result.find('td', attrs = {'class': 'quickdownload'}).find('a')
details = result.find('td', attrs = {'class' : 'name'}).find('a') details = result.find('td', attrs = {'class': 'name'}).find('a')
results.append({ results.append({
'id': link['href'].replace('/torrent/', ''), 'id': link['href'].replace('/torrent/', ''),
@ -52,8 +52,8 @@ class Base(TorrentProvider):
'url': self.urls['download'] % url['href'], 'url': self.urls['download'] % url['href'],
'detail_url': self.urls['download'] % details['href'], 'detail_url': self.urls['download'] % details['href'],
'size': self.parseSize(result.find_all('td')[4].string), 'size': self.parseSize(result.find_all('td')[4].string),
'seeders': tryInt(result.find('td', attrs = {'class' : 'seeders'}).string), 'seeders': tryInt(result.find('td', attrs = {'class': 'seeders'}).string),
'leechers': tryInt(result.find('td', attrs = {'class' : 'leechers'}).string), 'leechers': tryInt(result.find('td', attrs = {'class': 'leechers'}).string),
}) })
except: except:

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

@ -21,7 +21,7 @@ class Base(TorrentProvider):
'download': 'https://torrentshack.net/%s', 'download': 'https://torrentshack.net/%s',
} }
http_time_between_calls = 1 #seconds http_time_between_calls = 1 # Seconds
def _search(self, media, quality, results): def _search(self, media, quality, results):
@ -32,16 +32,16 @@ class Base(TorrentProvider):
html = BeautifulSoup(data) html = BeautifulSoup(data)
try: try:
result_table = html.find('table', attrs = {'id' : 'torrent_table'}) result_table = html.find('table', attrs = {'id': 'torrent_table'})
if not result_table: if not result_table:
return return
entries = result_table.find_all('tr', attrs = {'class' : 'torrent'}) entries = result_table.find_all('tr', attrs = {'class': 'torrent'})
for result in entries: for result in entries:
link = result.find('span', attrs = {'class' : 'torrent_name_link'}).parent link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent
url = result.find('td', attrs = {'class' : 'torrent_td'}).find('a') url = result.find('td', attrs = {'class': 'torrent_td'}).find('a')
results.append({ results.append({
'id': link['href'].replace('torrents.php?torrentid=', ''), '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' 'detail': '%s/api/movie.json?id=%s'
} }
http_time_between_calls = 1 #seconds http_time_between_calls = 1 # seconds
proxy_list = [ proxy_list = [
'http://yify.unlocktorrent.com', 'http://yify.unlocktorrent.com',
@ -81,7 +81,7 @@ config = [{
{ {
'name': 'enabled', 'name': 'enabled',
'type': 'enabler', 'type': 'enabler',
'default': 0 'default': False
}, },
{ {
'name': 'domain', 'name': 'domain',

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

@ -6,12 +6,11 @@
top: 0; top: 0;
text-align: right; text-align: right;
height: 100%; height: 100%;
border-bottom: 4px solid transparent;
transition: all .4s cubic-bezier(0.9,0,0.1,1); transition: all .4s cubic-bezier(0.9,0,0.1,1);
position: absolute; position: absolute;
z-index: 20; z-index: 20;
border: 1px solid transparent; border: 0 solid transparent;
border-width: 0 0 4px; border-bottom-width: 4px;
} }
.search_form:hover { .search_form:hover {
border-color: #047792; border-color: #047792;
@ -49,7 +48,6 @@
color: #FFF; color: #FFF;
font-size: 25px; font-size: 25px;
height: 100%; height: 100%;
padding: 10px;
width: 100%; width: 100%;
opacity: 0; opacity: 0;
padding: 0 40px 0 10px; padding: 0 40px 0 10px;

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

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

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

@ -14,9 +14,11 @@ log = CPLog(__name__)
class Searcher(SearcherBase): class Searcher(SearcherBase):
# noinspection PyMissingConstructor
def __init__(self): def __init__(self):
addEvent('searcher.protocols', self.getSearchProtocols) addEvent('searcher.protocols', self.getSearchProtocols)
addEvent('searcher.contains_other_quality', self.containsOtherQuality) addEvent('searcher.contains_other_quality', self.containsOtherQuality)
addEvent('searcher.correct_3d', self.correct3D)
addEvent('searcher.correct_year', self.correctYear) addEvent('searcher.correct_year', self.correctYear)
addEvent('searcher.correct_name', self.correctName) addEvent('searcher.correct_name', self.correctName)
addEvent('searcher.correct_words', self.correctWords) addEvent('searcher.correct_words', self.correctWords)
@ -123,6 +125,17 @@ class Searcher(SearcherBase):
return not (found.get(preferred_quality['identifier']) and len(found) == 1) 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): def correctYear(self, haystack, year, year_range):
if not isinstance(haystack, (list, tuple, set)): if not isinstance(haystack, (list, tuple, set)):

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

@ -26,7 +26,7 @@ var MovieList = new Class({
self.filter = self.options.filter || { self.filter = self.options.filter || {
'starts_with': null, 'starts_with': null,
'search': null 'search': null
} };
self.el = new Element('div.movies').adopt( self.el = new Element('div.movies').adopt(
self.title = self.options.title ? new Element('h2', { self.title = self.options.title ? new Element('h2', {
@ -52,7 +52,7 @@ var MovieList = new Class({
self.getMovies(); 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)) App.on('movie.deleted', self.movieDeleted.bind(self))
}, },
@ -96,7 +96,7 @@ var MovieList = new Class({
if(self.options.load_more) if(self.options.load_more)
self.scrollspy = new ScrollSpy({ self.scrollspy = new ScrollSpy({
min: function(){ min: function(){
var c = self.load_more.getCoordinates() var c = self.load_more.getCoordinates();
return c.top - window.document.getSize().y - 300 return c.top - window.document.getSize().y - 300
}, },
onEnter: self.loadMore.bind(self) onEnter: self.loadMore.bind(self)
@ -179,7 +179,7 @@ var MovieList = new Class({
m.fireEvent('injected'); m.fireEvent('injected');
self.movies.include(m) self.movies.include(m);
self.movies_added[movie._id] = true; self.movies_added[movie._id] = true;
}, },
@ -187,7 +187,7 @@ var MovieList = new Class({
var self = this; var self = this;
var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'; var chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
self.el.addClass('with_navigation') self.el.addClass('with_navigation');
self.navigation = new Element('div.alph_nav').adopt( self.navigation = new Element('div.alph_nav').adopt(
self.mass_edit_form = new Element('div.mass_edit_form').adopt( self.mass_edit_form = new Element('div.mass_edit_form').adopt(
@ -242,7 +242,7 @@ var MovieList = new Class({
this.addClass(a); this.addClass(a);
el.inject(el.getParent(), 'top'); el.inject(el.getParent(), 'top');
el.getSiblings().hide() el.getSiblings().hide();
setTimeout(function(){ setTimeout(function(){
el.getSiblings().setStyle('display', null); el.getSiblings().setStyle('display', null);
}, 100) }, 100)
@ -286,7 +286,7 @@ var MovieList = new Class({
'status': self.options.status 'status': self.options.status
}, self.filter), }, self.filter),
'onSuccess': function(json){ 'onSuccess': function(json){
available_chars = json.chars available_chars = json.chars;
available_chars.each(function(c){ available_chars.each(function(c){
self.letters[c.capitalize()].addClass('available') self.letters[c.capitalize()].addClass('available')
@ -300,7 +300,7 @@ var MovieList = new Class({
self.navigation_alpha = new Element('ul.numbers', { self.navigation_alpha = new Element('ul.numbers', {
'events': { 'events': {
'click:relay(li.available)': function(e, el){ 'click:relay(li.available)': function(e, el){
self.activateLetter(el.get('data-letter')) self.activateLetter(el.get('data-letter'));
self.getMovies(true) self.getMovies(true)
} }
} }
@ -318,7 +318,7 @@ var MovieList = new Class({
// All // All
self.letters['all'] = new Element('li.letter_all.available.active', { self.letters['all'] = new Element('li.letter_all.available.active', {
'text': 'ALL', 'text': 'ALL'
}).inject(self.navigation_alpha); }).inject(self.navigation_alpha);
// Chars // Chars
@ -334,7 +334,7 @@ var MovieList = new Class({
if (self.options.menu.length > 0) if (self.options.menu.length > 0)
self.options.menu.each(function(menu_item){ self.options.menu.each(function(menu_item){
self.navigation_menu.addLink(menu_item); self.navigation_menu.addLink(menu_item);
}) });
else else
self.navigation_menu.hide(); self.navigation_menu.hide();
@ -347,15 +347,15 @@ var MovieList = new Class({
movies = self.movies.length; movies = self.movies.length;
self.movies.each(function(movie){ self.movies.each(function(movie){
selected += movie.isSelected() ? 1 : 0 selected += movie.isSelected() ? 1 : 0
}) });
var indeterminate = selected > 0 && selected < movies, var indeterminate = selected > 0 && selected < movies,
checked = selected == movies && selected > 0; 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[checked ? 'check' : 'uncheck']();
self.mass_edit_select_class.element[indeterminate ? 'addClass' : 'removeClass']('indeterminate') self.mass_edit_select_class.element[indeterminate ? 'addClass' : 'removeClass']('indeterminate');
self.mass_edit_selected.set('text', selected); self.mass_edit_selected.set('text', selected);
}, },
@ -371,7 +371,7 @@ var MovieList = new Class({
'events': { 'events': {
'click': function(e){ 'click': function(e){
(e).preventDefault(); (e).preventDefault();
this.set('text', 'Deleting..') this.set('text', 'Deleting..');
Api.request('media.delete', { Api.request('media.delete', {
'data': { 'data': {
'id': ids.join(','), 'id': ids.join(','),
@ -383,7 +383,7 @@ var MovieList = new Class({
var erase_movies = []; var erase_movies = [];
self.movies.each(function(movie){ self.movies.each(function(movie){
if (movie.isSelected()){ if (movie.isSelected()){
$(movie).destroy() $(movie).destroy();
erase_movies.include(movie); erase_movies.include(movie);
} }
}); });
@ -410,7 +410,7 @@ var MovieList = new Class({
changeQualitySelected: function(){ changeQualitySelected: function(){
var self = this; var self = this;
var ids = self.getSelectedMovies() var ids = self.getSelectedMovies();
Api.request('movie.edit', { Api.request('movie.edit', {
'data': { 'data': {
@ -423,11 +423,11 @@ var MovieList = new Class({
refreshSelected: function(){ refreshSelected: function(){
var self = this; var self = this;
var ids = self.getSelectedMovies() var ids = self.getSelectedMovies();
Api.request('media.refresh', { Api.request('media.refresh', {
'data': { 'data': {
'id': ids.join(','), 'id': ids.join(',')
} }
}); });
}, },
@ -435,7 +435,7 @@ var MovieList = new Class({
getSelectedMovies: function(){ getSelectedMovies: function(){
var self = this; var self = this;
var ids = [] var ids = [];
self.movies.each(function(movie){ self.movies.each(function(movie){
if (movie.isSelected()) if (movie.isSelected())
ids.include(movie.get('_id')) ids.include(movie.get('_id'))
@ -459,11 +459,11 @@ var MovieList = new Class({
reset: function(){ reset: function(){
var self = this; var self = this;
self.movies = [] self.movies = [];
if(self.mass_edit_select) if(self.mass_edit_select)
self.calculateSelected() self.calculateSelected();
if(self.navigation_alpha) if(self.navigation_alpha)
self.navigation_alpha.getElements('.active').removeClass('active') self.navigation_alpha.getElements('.active').removeClass('active');
self.offset = 0; self.offset = 0;
if(self.scrollspy){ if(self.scrollspy){
@ -475,7 +475,7 @@ var MovieList = new Class({
activateLetter: function(letter){ activateLetter: function(letter){
var self = this; var self = this;
self.reset() self.reset();
self.letters[letter || 'all'].addClass('active'); self.letters[letter || 'all'].addClass('active');
self.filter.starts_with = letter; self.filter.starts_with = letter;
@ -487,7 +487,7 @@ var MovieList = new Class({
self.el self.el
.removeClass(self.current_view+'_list') .removeClass(self.current_view+'_list')
.addClass(new_view+'_list') .addClass(new_view+'_list');
self.current_view = new_view; self.current_view = new_view;
Cookie.write(self.options.identifier+'_view2', new_view, {duration: 1000}); 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); if(self.search_timer) clearTimeout(self.search_timer);
self.search_timer = (function(){ self.search_timer = (function(){
var search_value = self.navigation_search_input.get('value'); 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.activateLetter();
self.filter.search = search_value; self.filter.search = search_value;
@ -563,7 +563,7 @@ var MovieList = new Class({
if(self.loader_first){ if(self.loader_first){
var lf = self.loader_first; var lf = self.loader_first;
self.loader_first.addClass('hide') self.loader_first.addClass('hide');
self.loader_first = null; self.loader_first = null;
setTimeout(function(){ setTimeout(function(){
lf.destroy(); 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); var is_empty = self.movies.length == 0 && (self.total_movies == 0 || self.total_movies === undefined);
if(self.title) if(self.title)
self.title[is_empty ? 'hide' : 'show']() self.title[is_empty ? 'hide' : 'show']();
if(self.description) 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){ if(is_empty && self.options.on_empty_element){
self.options.on_empty_element.inject(self.loader_first || self.title || self.movie_list, 'after'); 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' 'z-index': '1'
} }
}).inject(self.movie, 'top').fade('hide'); }).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(){ toElement: function(){
@ -122,7 +106,7 @@ MA.Release = new Class({
}); });
if(!self.movie.data.releases || self.movie.data.releases.length == 0) if(!self.movie.data.releases || self.movie.data.releases.length == 0)
self.el.hide() self.el.hide();
else else
self.showHelper(); self.showHelper();
@ -164,7 +148,7 @@ MA.Release = new Class({
new Element('span.age', {'text': 'Age'}), new Element('span.age', {'text': 'Age'}),
new Element('span.score', {'text': 'Score'}), new Element('span.score', {'text': 'Score'}),
new Element('span.provider', {'text': 'Provider'}) new Element('span.provider', {'text': 'Provider'})
).inject(self.release_container) ).inject(self.release_container);
if(self.movie.data.releases) if(self.movie.data.releases)
self.movie.data.releases.each(function(release){ self.movie.data.releases.each(function(release){
@ -186,13 +170,13 @@ MA.Release = new Class({
} }
// Create release // Create release
var item = new Element('div', { release['el'] = new Element('div', {
'class': 'item '+release.status, 'class': 'item '+release.status,
'id': 'release_'+release._id 'id': 'release_'+release._id
}).adopt( }).adopt(
new Element('span.name', {'text': release_name, 'title': release_name}), 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.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.size', {'text': info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}),
new Element('span.age', {'text': self.get(release, 'age')}), new Element('span.age', {'text': self.get(release, 'age')}),
new Element('span.score', {'text': self.get(release, 'score')}), new Element('span.score', {'text': self.get(release, 'score')}),
@ -219,7 +203,6 @@ MA.Release = new Class({
} }
}) })
).inject(self.release_container); ).inject(self.release_container);
release['el'] = item;
if(release.status == 'ignored' || release.status == 'failed' || release.status == 'snatched'){ 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')) 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); status_el.set('text', new_status);
if(!q && (new_status == 'snatched' || new_status == 'seeding' || new_status == 'done')) 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)) { if(q && !q.hasClass(new_status)) {
q.removeClass(release.status).addClass(new_status); q.removeClass(release.status).addClass(new_status);
q.set('title', q.get('title').replace(release.status, new_status)); q.set('title', q.get('title').replace(release.status, new_status));
} }
} };
App.on('release.update_status', update_handle); App.on('release.update_status', update_handle);
@ -391,12 +374,11 @@ MA.Release = new Class({
}, },
ignore: function(release){ ignore: function(release){
var self = this;
Api.request('release.ignore', { Api.request('release.ignore', {
'data': { 'data': {
'id': release._id 'id': release._id
}, }
}) })
}, },
@ -456,7 +438,7 @@ MA.Trailer = new Class({
watch: function(offset){ watch: function(offset){
var self = this; 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({ var url = data_url.substitute({
'title': encodeURI(self.getTitle()), 'title': encodeURI(self.getTitle()),
'year': self.get('year'), 'year': self.get('year'),
@ -515,7 +497,7 @@ MA.Trailer = new Class({
} }
} }
} };
self.player.addEventListener('onStateChange', change_quality); self.player.addEventListener('onStateChange', change_quality);
} }
@ -532,7 +514,7 @@ MA.Trailer = new Class({
$(self.movie).setStyle('height', null); $(self.movie).setStyle('height', null);
setTimeout(function(){ setTimeout(function(){
self.container.destroy() self.container.destroy();
self.close_button.destroy(); self.close_button.destroy();
}, 1800) }, 1800)
} }
@ -661,7 +643,7 @@ MA.Edit = new Class({
self.movie.slide('out'); self.movie.slide('out');
} }
}) });
MA.Refresh = new Class({ MA.Refresh = new Class({
@ -847,7 +829,7 @@ MA.Files = new Class({
new Element('div.item.head').adopt( new Element('div.item.head').adopt(
new Element('span.name', {'text': 'File'}), new Element('span.name', {'text': 'File'}),
new Element('span.type', {'text': 'Type'}) new Element('span.type', {'text': 'Type'})
).inject(self.files_container) ).inject(self.files_container);
if(self.movie.data.releases) if(self.movie.data.releases)
Array.each(self.movie.data.releases, function(release){ 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 { .movies .data .quality span {
padding: 2px 3px; padding: 2px 3px;
font-weight: bold;
opacity: 0.5; opacity: 0.5;
font-size: 10px; font-size: 10px;
height: 16px; height: 16px;
@ -451,7 +450,6 @@
right: 20px; right: 20px;
line-height: 0; line-height: 0;
top: 0; top: 0;
display: block;
width: auto; width: auto;
opacity: 0; opacity: 0;
display: none; display: none;
@ -833,7 +831,6 @@
} }
.movies .alph_nav .search input { .movies .alph_nav .search input {
padding: 6px 5px;
width: 100%; width: 100%;
height: 44px; height: 44px;
display: inline-block; display: inline-block;
@ -841,7 +838,6 @@
background: none; background: none;
color: #444; color: #444;
font-size: 14px; font-size: 14px;
padding: 10px;
padding: 0 10px 0 30px; padding: 0 10px 0 30px;
border-bottom: 1px solid rgba(0,0,0,.08); border-bottom: 1px solid rgba(0,0,0,.08);
} }
@ -1043,7 +1039,6 @@
} }
.movies .progress > div .percentage { .movies .progress > div .percentage {
font-weight: bold;
display: inline-block; display: inline-block;
text-transform: uppercase; text-transform: uppercase;
font-weight: normal; font-weight: normal;

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

@ -23,7 +23,7 @@ var Movie = new Class({
addEvents: function(){ addEvents: function(){
var self = this; var self = this;
self.global_events = {} self.global_events = {};
// Do refresh with new data // Do refresh with new data
self.global_events['movie.update'] = function(notification){ self.global_events['movie.update'] = function(notification){
@ -32,7 +32,7 @@ var Movie = new Class({
self.busy(false); self.busy(false);
self.removeView(); self.removeView();
self.update.delay(2000, self, notification); self.update.delay(2000, self, notification);
} };
App.on('movie.update', self.global_events['movie.update']); App.on('movie.update', self.global_events['movie.update']);
// Add spinner on load / search // Add spinner on load / search
@ -40,20 +40,20 @@ var Movie = new Class({
self.global_events[listener] = function(notification){ 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))) 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); self.busy(true);
} };
App.on(listener, self.global_events[listener]); App.on(listener, self.global_events[listener]);
}) });
// Remove spinner // Remove spinner
self.global_events['movie.searcher.ended'] = function(notification){ self.global_events['movie.searcher.ended'] = function(notification){
if(notification.data && self.data._id == notification.data._id) if(notification.data && self.data._id == notification.data._id)
self.busy(false) self.busy(false)
} };
App.on('movie.searcher.ended', self.global_events['movie.searcher.ended']); App.on('movie.searcher.ended', self.global_events['movie.searcher.ended']);
// Reload when releases have updated // Reload when releases have updated
self.global_events['release.update_status'] = function(notification){ 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(data && self.data._id == data.movie_id){
if(!self.data.releases) if(!self.data.releases)
@ -62,7 +62,7 @@ var Movie = new Class({
self.data.releases.push({'quality': data.quality, 'status': data.status}); self.data.releases.push({'quality': data.quality, 'status': data.status});
self.updateReleases(); self.updateReleases();
} }
} };
App.on('release.update_status', self.global_events['release.update_status']); App.on('release.update_status', self.global_events['release.update_status']);
@ -73,7 +73,7 @@ var Movie = new Class({
self.el.destroy(); self.el.destroy();
delete self.list.movies_added[self.get('id')]; delete self.list.movies_added[self.get('id')];
self.list.movies.erase(self) self.list.movies.erase(self);
self.list.checkIfEmpty(); self.list.checkIfEmpty();
@ -117,18 +117,6 @@ var Movie = new Class({
}).inject(self.el, 'top').fade('hide'); }).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){ update: function(notification){
var self = this; var self = this;
@ -197,7 +185,7 @@ var Movie = new Class({
if(self.profile.data) if(self.profile.data)
self.profile.getTypes().each(function(type){ 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')){ if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){
q.addClass('finish'); q.addClass('finish');
q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.') 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(); self.updateReleases();
Object.each(self.options.actions, function(action, key){ 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) if(action.el)
self.actions.adopt(action) self.actions.adopt(action)
}); });
@ -222,11 +210,11 @@ var Movie = new Class({
self.data.releases.each(function(release){ 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; status = release.status;
if(!q && (status == 'snatched' || status == 'seeding' || status == 'done')) 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)){ if (q && !q.hasClass(status)){
q.addClass(status); q.addClass(status);
@ -236,13 +224,13 @@ var Movie = new Class({
}); });
}, },
addQuality: function(quality){ addQuality: function(quality, is_3d){
var self = this; var self = this;
var q = Quality.getQuality(quality); var q = Quality.getQuality(quality);
return new Element('span', { return new Element('span', {
'text': q.label, 'text': q.label + (is_3d ? ' 3D' : ''),
'class': 'q_'+q.identifier, 'class': 'q_'+q.identifier + (is_3d ? ' is_3d' : ''),
'title': '' 'title': ''
}).inject(self.quality); }).inject(self.quality);
@ -252,9 +240,9 @@ var Movie = new Class({
var self = this; var self = this;
if(self.data.title) if(self.data.title)
return self.getUnprefixedTitle(self.data.title) return self.getUnprefixedTitle(self.data.title);
else if(self.data.info.titles.length > 0) 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' return 'Unknown movie'
}, },
@ -275,12 +263,12 @@ var Movie = new Class({
self.el.addEvent('outerClick', function(){ self.el.addEvent('outerClick', function(){
self.removeView(); self.removeView();
self.slide('out') self.slide('out')
}) });
el.show(); el.show();
self.data_container.addClass('hide_right'); self.data_container.addClass('hide_right');
} }
else { else {
self.el.removeEvents('outerClick') self.el.removeEvents('outerClick');
setTimeout(function(){ setTimeout(function(){
if(self.el) if(self.el)
@ -297,7 +285,7 @@ var Movie = new Class({
if(self.el) if(self.el)
self.el self.el
.removeClass(self.view+'_view') .removeClass(self.view+'_view')
.addClass(new_view+'_view') .addClass(new_view+'_view');
self.view = new_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) if(info.titles)
info.titles.each(function(title){ info.titles.each(function(title){
@ -132,19 +132,19 @@ Block.Search.MovieItem = new Class({
if(!self.options_el.hasClass('set')){ if(!self.options_el.hasClass('set')){
if(self.info.in_library){ if(info.in_library){
var 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) in_library.include(release.quality)
}); });
} }
self.options_el.grab( self.options_el.grab(
new Element('div', { 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( }).adopt(
self.info.in_wanted && self.info.in_wanted.profile_id ? new Element('span.in_wanted', { info.in_wanted && 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') 'text': 'Already in wanted list: ' + Quality.getProfile(info.in_wanted.profile_id).get('label')
}) : (in_library ? new Element('span.in_library', { }) : (in_library ? new Element('span.in_library', {
'text': 'Already in library: ' + in_library.join(', ') 'text': 'Already in library: ' + in_library.join(', ')
}) : null), }) : null),
@ -172,7 +172,7 @@ Block.Search.MovieItem = new Class({
new Element('option', { new Element('option', {
'text': alt.title 'text': alt.title
}).inject(self.title_select) }).inject(self.title_select)
}) });
// Fill categories // Fill categories
@ -215,9 +215,9 @@ Block.Search.MovieItem = new Class({
loadingMask: function(){ loadingMask: function(){
var self = this; 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') 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): class Automation(AutomationBase):
enabled_option = 'automation_enabled' enabled_option = 'automation_enabled'
chart_enabled_option = 'chart_display_enabled'
http_time_between_calls = 2 http_time_between_calls = 2
interval = 1800 interval = 1800
@ -20,6 +21,7 @@ class Automation(AutomationBase):
def __init__(self): def __init__(self):
addEvent('automation.get_movies', self._getMovies) addEvent('automation.get_movies', self._getMovies)
addEvent('automation.get_chart_list', self._getChartList)
def _getMovies(self): def _getMovies(self):
@ -34,6 +36,13 @@ class Automation(AutomationBase):
return self.getIMDBids() 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): def search(self, name, year = None, imdb_only = False):
prop_name = 'automation.cached.%s.%s' % (name, year) prop_name = 'automation.cached.%s.%s' % (name, year)
@ -94,5 +103,9 @@ class Automation(AutomationBase):
def getIMDBids(self): def getIMDBids(self):
return [] return []
def getChartList(self):
# Example return: [ {'name': 'Display name of list', 'url': 'http://example.com/', 'order': 1, 'list': []} ]
return
def canCheck(self): def canCheck(self):
return time.time() > self.last_checked + self.interval return time.time() > self.last_checked + self.interval

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

@ -14,6 +14,8 @@ class Bluray(Automation, RSS):
interval = 1800 interval = 1800
rss_url = 'http://www.blu-ray.com/rss/newreleasesfeed.xml' rss_url = 'http://www.blu-ray.com/rss/newreleasesfeed.xml'
backlog_url = 'http://www.blu-ray.com/movies/movies.php?show=newreleases&page=%s' 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): def getIMDBids(self):
@ -77,6 +79,32 @@ class Bluray(Automation, RSS):
return movies 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 = [{ config = [{
'name': 'bluray', 'name': 'bluray',
'groups': [ '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 break
for movie in this_watch_list: 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(): if not 'next page' in data.lower():
break break

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

@ -105,6 +105,16 @@ class IMDBAutomation(IMDBBase):
'top250': 'http://www.imdb.com/chart/top', 'top250': 'http://www.imdb.com/chart/top',
'boxoffice': 'http://www.imdb.com/chart/', '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'] first_table = ['boxoffice']
@ -144,6 +154,46 @@ class IMDBAutomation(IMDBBase):
return movies 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 = [{ config = [{
'name': 'imdb', 'name': 'imdb',
'groups': [ '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'}): for movie in soup.find_all('a', attrs = {'class': 'frame'}):
match = removeEmpty(self.pattern.split(movie['title'])) 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 return movies

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

@ -104,6 +104,9 @@ class MovieResultModifier(Plugin):
if media.get('status') == 'active': if media.get('status') == 'active':
temp['in_wanted'] = media 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): for release in fireEvent('release.for_media', media['_id'], single = True):
if release.get('status') == 'done': if release.get('status') == 'done':
if not temp['in_library']: if not temp['in_library']:

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

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

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

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

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

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

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

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

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

@ -12,6 +12,7 @@ autoload = 'ThePirateBay'
class ThePirateBay(MovieProvider, Base): class ThePirateBay(MovieProvider, Base):
cat_ids = [ cat_ids = [
([209], ['3d']),
([207], ['720p', '1080p']), ([207], ['720p', '1080p']),
([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']), ([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
([201, 207], ['brrip']), ([201, 207], ['brrip']),

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

@ -16,5 +16,6 @@ class TorrentDay(MovieProvider, Base):
([3], ['dvdr']), ([3], ['dvdr']),
([5], ['bd50']), ([5], ['bd50']),
] ]
def buildUrl(self, media): def buildUrl(self, media):
return fireEvent('library.query', media, single = True) 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): def buildUrl(self, media, quality):
return ( return (
tryUrlencode(fireEvent('library.query', media, single = True)), 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): def buildUrl(self, media, quality):
query = (tryUrlencode(fireEvent('library.query', media, single = True)), query = (tryUrlencode(fireEvent('library.query', media, single = True)),
self.getCatId(quality['identifier'])[0], self.getCatId(quality)[0],
self.getSceneOnly()) self.getSceneOnly())
return query 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 return result_data
def findViaAlternative(self, group): def findViaAlternative(self, group):
results = {'480p':[], '720p':[], '1080p':[]} results = {'480p': [], '720p': [], '1080p': []}
movie_name = getTitle(group) 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 ret = False
index = 0
for q_identifier in profile.get('qualities'): for q_identifier in profile.get('qualities'):
index = profile['qualities'].index(q_identifier)
quality_custom = { quality_custom = {
'quality': q_identifier, 'quality': q_identifier,
'finish': profile['finish'][index], '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']): 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) too_early_to_search.append(q_identifier)
continue continue
@ -168,6 +171,9 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
quality = fireEvent('quality.single', identifier = q_identifier, single = True) quality = fireEvent('quality.single', identifier = q_identifier, single = True)
log.info('Search for %s in %s', (default_title, quality['label'])) 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 [] results = fireEvent('searcher.search', search_protocols, movie, quality, single = True) or []
if len(results) == 0: if len(results) == 0:
log.debug('Nothing found for %s in %s', (default_title, quality['label'])) 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): if not fireEvent('searcher.correct_words', nzb['name'], media, single = True):
return False 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 # Contains lower quality string
if fireEvent('searcher.contains_other_quality', nzb, movie_year = media['info']['year'], preferred_quality = preferred_quality, single = True): 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'])) log.info2('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
return False 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 # File to small
if nzb['size'] and tryInt(preferred_quality['size_min']) > tryInt(nzb['size']): 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'])) 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 return False
# Provider specific functions # Provider specific functions
get_more = nzb.get('get_more_info') get_more = nzb.get('get_more_info')
if get_more: if get_more:

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

@ -1,4 +1,7 @@
.suggestions { .suggestions {
clear: both;
padding-top: 10px;
margin-bottom: 30px;
} }
.suggestions > h2 { .suggestions > h2 {
@ -91,7 +94,6 @@
padding: 0 3px 10px 0; padding: 0 3px 10px 0;
} }
.suggestions .media_result .data:before { .suggestions .media_result .data:before {
bottom: 0;
content: ''; content: '';
display: block; display: block;
height: 10px; height: 10px;

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', { self.api_request = Api.request('suggestion.view', {
'onComplete': self.fill.bind(self) 'onComplete': self.fill.bind(self)
@ -116,7 +115,7 @@ var SuggestList = new Class({
} }
} }
}) : null }) : null
) );
$(m).inject(self.el); $(m).inject(self.el);
@ -150,4 +149,4 @@ var SuggestList = new Class({
return this.el; return this.el;
} }
}) });

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

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

2
couchpotato/core/notifications/growl.py

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

1
couchpotato/core/notifications/nmj.py

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

9
couchpotato/core/notifications/pushover.py

@ -25,6 +25,7 @@ class Pushover(Notification):
'token': self.app_token, 'token': self.app_token,
'message': toUnicode(message), 'message': toUnicode(message),
'priority': self.conf('priority'), 'priority': self.conf('priority'),
'sound': self.conf('sound'),
} }
if data and data.get('identifier'): if data and data.get('identifier'):
@ -33,8 +34,7 @@ class Pushover(Notification):
'url_title': toUnicode('%s on IMDb' % getTitle(data)), 'url_title': toUnicode('%s on IMDb' % getTitle(data)),
}) })
http_handler.request('POST', http_handler.request('POST', '/1/messages.json',
"/1/messages.json",
headers = {'Content-type': 'application/x-www-form-urlencoded'}, headers = {'Content-type': 'application/x-www-form-urlencoded'},
body = tryUrlencode(api_data) body = tryUrlencode(api_data)
) )
@ -83,6 +83,11 @@ config = [{
'advanced': True, 'advanced': True,
'description': 'Also send message when movie is snatched.', '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'); ).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 self.use_json_notifications[host] = False
# send the text message # 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: for r in resp:
if r.get('result') and r['result'] == 'OK': if r.get('result') and r['result'] == 'OK':
log.debug('Message delivered successfully!') 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 prop_name = 'automation.added.%s' % imdb_id
added = Env.prop(prop_name, default = False) added = Env.prop(prop_name, default = False)
if not added: 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: if added_movie:
movie_ids.append(added_movie['id']) movie_ids.append(added_movie['_id'])
Env.prop(prop_name, True) Env.prop(prop_name, True)
for movie_id in movie_ids: for movie_id in movie_ids:

5
couchpotato/core/plugins/base.py

@ -43,8 +43,8 @@ class Plugin(object):
http_failed_disabled = {} http_failed_disabled = {}
http_opener = requests.Session() http_opener = requests.Session()
def __new__(typ, *args, **kwargs): def __new__(cls, *args, **kwargs):
new_plugin = super(Plugin, typ).__new__(typ) new_plugin = super(Plugin, cls).__new__(cls)
new_plugin.registerPlugin() new_plugin.registerPlugin()
return new_plugin return new_plugin
@ -349,6 +349,7 @@ class Plugin(object):
now = time.time() now = time.time()
file_too_new = False file_too_new = False
file_time = []
for cur_file in files: for cur_file in files:
# File got removed while checking # 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 \ 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/") pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/")
else: else:
import win32file #@UnresolvedImport # noinspection PyUnresolvedReferences
import win32file
autoload = 'FileBrowser' autoload = 'FileBrowser'

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

@ -69,7 +69,7 @@
} }
#category_ordering li .handle { #category_ordering li .handle {
background: url('../../static/profile_plugin/handle.png') center; background: url('../../images/handle.png') center;
width: 20px; width: 20px;
float: right; float: right;
} }

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

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

4
couchpotato/core/plugins/dashboard.py

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

1
couchpotato/core/plugins/file.py

@ -1,5 +1,4 @@
import os.path import os.path
import time
import traceback import traceback
from couchpotato import get_db from couchpotato import get_db

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

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

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

@ -50,7 +50,7 @@
overflow: hidden; overflow: hidden;
line-height: 150%; line-height: 150%;
font-size: 11px; 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 { .page.log .container .error {

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

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

8
couchpotato/core/plugins/manage.py

@ -160,7 +160,7 @@ class Manage(Plugin):
except: except:
log.error('Failed updating library: %s', (traceback.format_exc())) 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 = {} delete_me = {}
@ -171,14 +171,12 @@ class Manage(Plugin):
for delete in delete_me: for delete in delete_me:
del self.in_progress[delete] del self.in_progress[delete]
if len(self.in_progress) == 0:
break
time.sleep(1) time.sleep(1)
fireEvent('notify.frontend', type = 'manage.updating', data = False) fireEvent('notify.frontend', type = 'manage.updating', data = False)
self.in_progress = False self.in_progress = False
# noinspection PyDefaultArgument
def createAddToLibrary(self, folder, added_identifiers = []): def createAddToLibrary(self, folder, added_identifiers = []):
def addToLibrary(group, total_found, to_go): def addToLibrary(group, total_found, to_go):
@ -219,7 +217,7 @@ class Manage(Plugin):
pr = self.in_progress[folder] pr = self.in_progress[folder]
pr['to_go'] -= 1 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']) 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), 'core': kwargs.get('core', False),
'qualities': [], 'qualities': [],
'wait_for': [], 'wait_for': [],
'finish': [] 'finish': [],
'3d': []
} }
# Update types # Update types
@ -88,6 +89,7 @@ class ProfilePlugin(Plugin):
profile['qualities'].append(type.get('quality')) profile['qualities'].append(type.get('quality'))
profile['wait_for'].append(tryInt(type.get('wait_for'))) profile['wait_for'].append(tryInt(type.get('wait_for')))
profile['finish'].append((tryInt(type.get('finish')) == 1) if order > 0 else True) profile['finish'].append((tryInt(type.get('finish')) == 1) if order > 0 else True)
profile['3d'].append(tryInt(type.get('3d')))
order += 1 order += 1
id = kwargs.get('id') id = kwargs.get('id')
@ -184,6 +186,14 @@ class ProfilePlugin(Plugin):
}, { }, {
'label': 'SD', 'label': 'SD',
'qualities': ['dvdrip', 'dvdr'] '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 # Create default quality profile
@ -197,12 +207,15 @@ class ProfilePlugin(Plugin):
'order': order, 'order': order,
'qualities': profile.get('qualities'), 'qualities': profile.get('qualities'),
'finish': [], 'finish': [],
'wait_for': [] 'wait_for': [],
'3d': []
} }
threed = profile.get('3d', [])
for q in profile.get('qualities'): for q in profile.get('qualities'):
pro['finish'].append(True) pro['finish'].append(True)
pro['wait_for'].append(0) pro['wait_for'].append(0)
pro['3d'].append(threed.pop() if threed else False)
db.insert(pro) db.insert(pro)
order += 1 order += 1

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

@ -29,11 +29,10 @@
.profile .qualities { .profile .qualities {
min-height: 80px; min-height: 80px;
padding-top: 0;
} }
.profile .formHint { .profile .formHint {
width: 250px !important; width: 210px !important;
vertical-align: top !important; vertical-align: top !important;
margin: 0 !important; margin: 0 !important;
padding-left: 3px !important; padding-left: 3px !important;
@ -78,21 +77,55 @@
} }
.profile .quality_type select { .profile .quality_type select {
width: 186px; width: 120px;
margin-left: -1px; 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; 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 { .profile .types .type .handle {
background: url('../../static/profile_plugin/handle.png') center; background: url('../../images/handle.png') center;
display: inline-block; display: inline-block;
height: 20px; height: 20px;
width: 20px; width: 20px;
cursor: -webkit-grab;
cursor: -moz-grab; cursor: -moz-grab;
cursor: -webkit-grab;
cursor: grab; cursor: grab;
margin: 0; margin: 0;
} }
@ -106,6 +139,9 @@
font-size: 13px; font-size: 13px;
color: #fd5353; color: #fd5353;
} }
.profile .types .type:not(.allow_3d) .delete {
margin-left: 55px;
}
.profile .types .type:hover:not(.is_empty) .delete { .profile .types .type:hover:not(.is_empty) .delete {
visibility: visible; visibility: visible;
@ -144,7 +180,7 @@
} }
#profile_ordering li .handle { #profile_ordering li .handle {
background: url('../../static/profile_plugin/handle.png') center; background: url('../../images/handle.png') center;
width: 20px; width: 20px;
float: right; float: right;
} }

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

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

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

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

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

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

20
couchpotato/core/plugins/release/main.py

@ -343,6 +343,10 @@ class Release(Plugin):
found_releases = [] found_releases = []
is_3d = False
try: is_3d = quality['custom']['3d']
except: pass
for rel in search_results: for rel in search_results:
rel_identifier = md5(rel['url']) rel_identifier = md5(rel['url'])
@ -353,6 +357,7 @@ class Release(Plugin):
'identifier': rel_identifier, 'identifier': rel_identifier,
'media_id': media.get('_id'), 'media_id': media.get('_id'),
'quality': quality.get('identifier'), 'quality': quality.get('identifier'),
'is_3d': is_3d,
'status': rel.get('status', 'available'), 'status': rel.get('status', 'available'),
'last_edit': int(time.time()), 'last_edit': int(time.time()),
'info': {} 'info': {}
@ -438,6 +443,17 @@ class Release(Plugin):
def forMedia(self, media_id): def forMedia(self, media_id):
db = get_db() db = get_db()
raw_releases = list(db.get_many('release', media_id, with_doc = True))
releases = []
for r in raw_releases:
releases.append(r['doc'])
releases = sorted(releases, key = lambda k: k.get('info', {}).get('score', 0), reverse = True)
# Sort based on preferred search method
download_preference = self.conf('preferred_method', section = 'searcher')
if download_preference != 'both':
releases = sorted(releases, key = lambda k: k.get('info', {}).get('protocol', '')[:3], reverse = (download_preference == 'torrent'))
for release in db.get_many('release', media_id, with_doc = True): return releases
yield release['doc']

32
couchpotato/core/plugins/renamer.py

@ -148,7 +148,7 @@ class Renamer(Plugin):
log.debug('The provided media folder %s does not exist. Trying to find it in the \'from\' folder.', media_folder) log.debug('The provided media folder %s does not exist. Trying to find it in the \'from\' folder.', media_folder)
# Update to the from folder # Update to the from folder
if len(release_download.get('files'), []) == 1: if len(release_download.get('files', [])) == 1:
new_media_folder = from_folder new_media_folder = from_folder
else: else:
new_media_folder = os.path.join(from_folder, os.path.basename(media_folder)) new_media_folder = os.path.join(from_folder, os.path.basename(media_folder))
@ -190,6 +190,8 @@ class Renamer(Plugin):
release_files = release_download.get('files', []) release_files = release_download.get('files', [])
if release_files: if release_files:
files = release_files
# If there is only one file in the torrent, the downloader did not create a subfolder # If there is only one file in the torrent, the downloader did not create a subfolder
if len(release_files) == 1: if len(release_files) == 1:
folder = media_folder folder = media_folder
@ -204,6 +206,7 @@ class Renamer(Plugin):
db = get_db() db = get_db()
# Extend the download info with info stored in the downloaded release # Extend the download info with info stored in the downloaded release
if release_download:
release_download = self.extendReleaseDownload(release_download) release_download = self.extendReleaseDownload(release_download)
# Unpack any archives # Unpack any archives
@ -248,7 +251,7 @@ class Renamer(Plugin):
'profile_id': None 'profile_id': None
}, search_after = False, status = 'done', single = True) }, search_after = False, status = 'done', single = True)
else: else:
group['media'] = fireEvent('movie.update_info', identifier = getIdentifier(group['media']), single = True) group['media'] = fireEvent('movie.update_info', media_id = group['media'].get('_id'), single = True)
if not group['media'] or not group['media'].get('_id'): if not group['media'] or not group['media'].get('_id'):
log.error('Could not rename, no library item to work with: %s', group_identifier) log.error('Could not rename, no library item to work with: %s', group_identifier)
@ -577,11 +580,11 @@ class Renamer(Plugin):
# Remove matching releases # Remove matching releases
for release in remove_releases: for release in remove_releases:
log.debug('Removing release %s', release.identifier) log.debug('Removing release %s', release.get('identifier'))
try: try:
db.delete(release) db.delete(release)
except: except:
log.error('Failed removing %s: %s', (release.identifier, traceback.format_exc())) log.error('Failed removing %s: %s', (release, traceback.format_exc()))
if group['dirname'] and group['parentdir'] and not self.downloadIsTorrent(release_download): if group['dirname'] and group['parentdir'] and not self.downloadIsTorrent(release_download):
if media_folder: if media_folder:
@ -652,13 +655,13 @@ Remove it if you want it to be renamed (again, or at least let it try again)
tag_files = release_download.get('files', []) tag_files = release_download.get('files', [])
# Tag all files in release folder # Tag all files in release folder
else: elif release_download['folder']:
for root, folders, names in scandir.walk(release_download['folder']): for root, folders, names in scandir.walk(release_download['folder']):
tag_files.extend([os.path.join(root, name) for name in names]) tag_files.extend([os.path.join(root, name) for name in names])
for filename in tag_files: for filename in tag_files:
# Dont tag .ignore files # Don't tag .ignore files
if os.path.splitext(filename)[1] == '.ignore': if os.path.splitext(filename)[1] == '.ignore':
continue continue
@ -682,19 +685,20 @@ Remove it if you want it to be renamed (again, or at least let it try again)
return False return False
elif isinstance(release_download, dict): elif isinstance(release_download, dict):
folder = release_download['folder']
if not os.path.isdir(folder):
return False
# Untag download_files if they are known # Untag download_files if they are known
if release_download.get('files'): if release_download.get('files'):
tag_files = release_download.get('files', []) tag_files = release_download.get('files', [])
# Untag all files in release folder # Untag all files in release folder
else: else:
for root, folders, names in scandir.walk(release_download['folder']): for root, folders, names in scandir.walk(folder):
tag_files.extend([sp(os.path.join(root, name)) for name in names if not os.path.splitext(name)[1] == '.ignore']) tag_files.extend([sp(os.path.join(root, name)) for name in names if not os.path.splitext(name)[1] == '.ignore'])
folder = release_download['folder']
if not os.path.isdir(folder):
return False
if not folder: if not folder:
return False return False
@ -729,7 +733,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
# Find tag on all files in release folder # Find tag on all files in release folder
else: else:
for root, folders, names in scandir.walk(release_download['folder']): for root, folders, names in scandir.walk(folder):
tag_files.extend([sp(os.path.join(root, name)) for name in names if not os.path.splitext(name)[1] == '.ignore']) tag_files.extend([sp(os.path.join(root, name)) for name in names if not os.path.splitext(name)[1] == '.ignore'])
# Find all .ignore files in folder # Find all .ignore files in folder
@ -867,7 +871,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
release_downloads = fireEvent('download.status', download_ids, merge = True) if download_ids else [] release_downloads = fireEvent('download.status', download_ids, merge = True) if download_ids else []
if len(no_status_support) > 0: if len(no_status_support) > 0:
log.debug('Download status functionality is not implemented for one of the active downloaders: %s', no_status_support) log.debug('Download status functionality is not implemented for one of the active downloaders: %s', list(set(no_status_support)))
if not release_downloads: if not release_downloads:
if fire_scan: if fire_scan:
@ -1016,7 +1020,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
if release_download['pause'] and self.conf('file_action') == 'link': if release_download['pause'] and self.conf('file_action') == 'link':
fireEvent('download.pause', release_download = release_download, pause = False, single = True) fireEvent('download.pause', release_download = release_download, pause = False, single = True)
if release_download['process_complete']: if release_download['process_complete']:
#First make sure the files were succesfully processed # First make sure the files were successfully processed
if not self.hastagRelease(release_download = release_download, tag = 'failed_rename'): if not self.hastagRelease(release_download = release_download, tag = 'failed_rename'):
# Remove the seeding tag if it exists # Remove the seeding tag if it exists
self.untagRelease(release_download = release_download, tag = 'renamed_already') self.untagRelease(release_download = release_download, tag = 'renamed_already')

11
couchpotato/core/plugins/scanner.py

@ -28,7 +28,7 @@ class Scanner(Plugin):
ignored_in_path = [os.path.sep + 'extracted' + os.path.sep, 'extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', ignored_in_path = [os.path.sep + 'extracted' + os.path.sep, 'extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_',
'_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo', '_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo',
'thumbs.db', 'ehthumbs.db', 'desktop.ini'] #unpacking, smb-crap, hidden files 'thumbs.db', 'ehthumbs.db', 'desktop.ini'] # unpacking, smb-crap, hidden files
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate'] ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
extensions = { extensions = {
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'], 'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],
@ -85,7 +85,7 @@ class Scanner(Plugin):
'hdtv': ['hdtv'] 'hdtv': ['hdtv']
} }
clean = '[ _\,\.\(\)\[\]\-]?(extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip' \ clean = '[ _\,\.\(\)\[\]\-]?(3d|hsbs|sbs|extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip' \
'|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)' '|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
multipart_regex = [ multipart_regex = [
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1 '[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
@ -129,7 +129,7 @@ class Scanner(Plugin):
check_file_date = True check_file_date = True
try: try:
files = [] files = []
for root, dirs, walk_files in scandir.walk(folder): for root, dirs, walk_files in scandir.walk(folder, followlinks=True):
files.extend([sp(os.path.join(root, filename)) for filename in walk_files]) files.extend([sp(os.path.join(root, filename)) for filename in walk_files])
# Break if CP wants to shut down # Break if CP wants to shut down
@ -589,7 +589,7 @@ class Scanner(Plugin):
if len(movie) > 0: if len(movie) > 0:
imdb_id = movie[0].get('imdb') imdb_id = movie[0].get('imdb')
log.debug('Found movie via search: %s', cur_file) log.debug('Found movie via search: %s', identifier)
if imdb_id: break if imdb_id: break
else: else:
log.debug('Identifier to short to use for search: %s', identifier) log.debug('Identifier to short to use for search: %s', identifier)
@ -597,7 +597,7 @@ class Scanner(Plugin):
if imdb_id: if imdb_id:
try: try:
db = get_db() db = get_db()
return db.get('media', imdb_id, with_doc = True)['doc'] return db.get('media', 'imdb-%s' % imdb_id, with_doc = True)['doc']
except: except:
log.debug('Movie "%s" not in library, just getting info', imdb_id) log.debug('Movie "%s" not in library, just getting info', imdb_id)
return { return {
@ -835,6 +835,7 @@ class Scanner(Plugin):
cleaned = ' '.join(re.split('\W+', simplifyString(release_name))) cleaned = ' '.join(re.split('\W+', simplifyString(release_name)))
cleaned = re.sub(self.clean, ' ', cleaned) cleaned = re.sub(self.clean, ' ', cleaned)
year = None
for year_str in [file_name, release_name, cleaned]: for year_str in [file_name, release_name, cleaned]:
if not year_str: continue if not year_str: continue
year = self.findYear(year_str) year = self.findYear(year_str)

2
couchpotato/core/plugins/userscript/bookmark.js → couchpotato/core/plugins/userscript/bookmark.js_tmpl

@ -32,7 +32,7 @@ var isCorrectUrl = function() {
} }
var addUserscript = function() { var addUserscript = function() {
// Add window param // Add window param
document.body.setAttribute('cp_auto_open', true) document.body.setAttribute('cp_auto_open', 'true')
// Load userscript // Load userscript
var e = document.createElement('script'); var e = document.createElement('script');

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

@ -15,7 +15,7 @@ log = CPLog(__name__)
class Userscript(Plugin): class Userscript(Plugin):
version = 4 version = 5
def __init__(self): def __init__(self):
addApiView('userscript.get/(.*)/(.*)', self.getUserScript, static = True) addApiView('userscript.get/(.*)/(.*)', self.getUserScript, static = True)
@ -35,7 +35,7 @@ class Userscript(Plugin):
'host': host, 'host': host,
} }
return self.renderTemplate(__file__, 'bookmark.js', **params) return self.renderTemplate(__file__, 'bookmark.js_tmpl', **params)
def getIncludes(self, **kwargs): def getIncludes(self, **kwargs):
@ -60,7 +60,7 @@ class Userscript(Plugin):
'host': '%s://%s' % (self.request.protocol, self.request.headers.get('X-Forwarded-Host') or self.request.headers.get('host')), 'host': '%s://%s' % (self.request.protocol, self.request.headers.get('X-Forwarded-Host') or self.request.headers.get('host')),
} }
script = klass.renderTemplate(__file__, 'template.js', **params) script = klass.renderTemplate(__file__, 'template.js_tmpl', **params)
klass.createFile(os.path.join(Env.get('cache_dir'), 'couchpotato.user.js'), script) klass.createFile(os.path.join(Env.get('cache_dir'), 'couchpotato.user.js'), script)
self.redirect(Env.get('api_base') + 'file.cache/couchpotato.user.js') self.redirect(Env.get('api_base') + 'file.cache/couchpotato.user.js')

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

Loading…
Cancel
Save