diff --git a/README.md b/README.md index 570f97e..aebf591 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Once a movie is found, it will send it to SABnzbd or download the torrent to a s CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed. -Windows, see [the CP forum](http://couchpota.to/forum/showthread.php?tid=14) for more details: +Windows, see [the CP forum](http://couchpota.to/forum/viewtopic.php?t=14) for more details: * Install [Python 2.7](http://www.python.org/download/releases/2.7.3/) * Then install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/) diff --git a/couchpotato/core/downloaders/deluge.py b/couchpotato/core/downloaders/deluge.py index 631925f..aaca40e 100644 --- a/couchpotato/core/downloaders/deluge.py +++ b/couchpotato/core/downloaders/deluge.py @@ -159,7 +159,7 @@ class Deluge(DownloaderBase): # If an user opts to seed a torrent forever (usually associated to private trackers usage), stop_ratio will be 0 or -1 (depending on Deluge version). # In this scenario the status of the torrent would never change from BUSY to SEEDING. # The last check takes care of this case. - if torrent['is_seed'] and ((tryFloat(torrent['ratio']) < tryFloat(torrent['stop_ratio'])) or (tryFloat(torrent['stop_ratio']) <= 0)): + if torrent['is_seed'] and ((tryFloat(torrent['ratio']) < tryFloat(torrent['stop_ratio'])) or (tryFloat(torrent['stop_ratio']) < 0)): # We have torrent['seeding_time'] to work out what the seeding time is, but we do not # have access to the downloader seed_time, as with deluge we have no way to pass it # when the torrent is added. So Deluge will only look at the ratio. diff --git a/couchpotato/core/media/_base/providers/torrent/iptorrents.py b/couchpotato/core/media/_base/providers/torrent/iptorrents.py index 1197838..e3331ef 100644 --- a/couchpotato/core/media/_base/providers/torrent/iptorrents.py +++ b/couchpotato/core/media/_base/providers/torrent/iptorrents.py @@ -14,11 +14,11 @@ log = CPLog(__name__) class Base(TorrentProvider): urls = { - 'test': 'https://iptorrents.eu/', - 'base_url': 'https://iptorrents.eu', - 'login': 'https://iptorrents.eu/take_login.php', - 'login_check': 'https://iptorrents.eu/oldinbox.php', - 'search': 'https://iptorrents.eu/t?%s%%s&q=%s&qf=ti#torrents&p=%%d', + 'test': 'https://iptorrents.com/', + 'base_url': 'https://iptorrents.com', + 'login': 'https://iptorrents.com/take_login.php', + 'login_check': 'https://iptorrents.com/oldinbox.php', + 'search': 'https://iptorrents.com/t?%s%%s&q=%s&qf=ti#torrents&p=%%d', } http_time_between_calls = 1 # Seconds @@ -36,6 +36,8 @@ class Base(TorrentProvider): log.warning('Unable to find category ids for identifier "%s"', quality.get('identifier')) return None + query = query.replace('"', '') + return self.urls['search'] % ("&".join(("%d=" % x) for x in cat_ids), tryUrlencode(query).replace('%', '%%')) def _searchOnTitle(self, title, media, quality, results): @@ -61,7 +63,7 @@ class Base(TorrentProvider): final_page_link = next_link.previous_sibling.previous_sibling pages = int(final_page_link.string) - result_table = html.find('table', attrs = {'id': 'torrents'}) + result_table = html.find('table', attrs={'id': 'torrents'}) if not result_table or 'nothing found!' in data.lower(): return @@ -121,7 +123,7 @@ config = [{ 'tab': 'searcher', 'list': 'torrent_providers', 'name': 'IPTorrents', - 'description': 'IPTorrents', + 'description': 'IPTorrents', 'wizard': True, 'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABRklEQVR42qWQO0vDUBiG8zeKY3EqQUtNO7g0J6ZJ1+ifKIIFQXAqDYKCyaaYxM3udrZLHdRFhXrZ6liCW6mubfk874EESgqaeOCF7/Y8hEh41aq6yZi2nyZgBGya9XKtZs4No05pAkZV2YbEmyMMsoSxLQeC46wCTdPPY4HruPQyGIhF97qLWsS78Miydn4XdK46NJ9OsQAYBzMIMf8MQ9wtCnTdWCaIDx/u7uljOIQEe0hiIWPamSTLay3+RxOCSPI9+RJAo7Er9r2bnqjBFAqyK+VyK4f5/Cr5ni8OFKVCz49PFI5GdNvvU7ttE1M1zMU+8AMqFksEhrMnQsBDzqmDAwzx2ehRLwT7yyCI+vSC99c3mozH1NxrJgWWtR1BOECfEJSVCm6WCzJGCA7+IWhBsM4zywDPwEp4vCjx2DzBH2ODAfsDb33Ps6dQwJgAAAAASUVORK5CYII=', 'options': [ diff --git a/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py b/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py index 7ec1080..a4df2cc 100644 --- a/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py +++ b/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py @@ -30,9 +30,29 @@ class Base(TorrentMagnetProvider): cat_backup_id = None proxy_list = [ + 'http://flowtorrent.com', + 'http://katcr.to/span', + 'http://dx-torrente.com', + 'https://kickass.unblocked.vip', + 'https://katcr.co', + 'https://kat.how', + 'https://kickass.cd', + 'https://kickass.unlockproject.online', + 'https://kickasstorrents.video', + 'https://kat.al', + 'https://katproxy.al', + 'https://kattor.xyz', + 'https://kickass.unblocked.video', + 'https://kickass.unblocked.rocks', + 'https://kickass.immunicity.live', + 'https://kickass.immunicity.red', + 'https://kickass.immunicity.video', + 'https://kickass.bypassed.live', + 'https://kickass.bypassed.video', + 'https://kickass.bypassed.red', 'https://kat.cr', 'https://kickass.unblocked.pw/', - 'https://katproxy.com', + 'https://katproxy.com' ] def _search(self, media, quality, results): diff --git a/couchpotato/core/media/_base/providers/torrent/yts.py b/couchpotato/core/media/_base/providers/torrent/yts.py new file mode 100644 index 0000000..f4567a9 --- /dev/null +++ b/couchpotato/core/media/_base/providers/torrent/yts.py @@ -0,0 +1,136 @@ +from datetime import datetime +from couchpotato.core.helpers.variable import tryInt +from couchpotato.core.logger import CPLog +from couchpotato.core.helpers.variable import getTitle +from couchpotato.core.media._base.providers.torrent.base import TorrentMagnetProvider +import random + +log = CPLog(__name__) + + +class Base(TorrentMagnetProvider): + # Only qualities allowed: 720p/1080p/3D - the rest will fail. + # All YTS.ag torrents are verified + urls = { + 'detail': 'https://yts.ag/api#list_movies', + 'search': 'https://yts.ag/api/v2/list_movies.json?query_term=%s&limit=%s&page=%s' + } + + def _search(self, movie, quality, results): + limit = 10 + page = 1 + data = self.getJsonData(self.urls['search'] % (getTitle(movie), limit, page)) + + if data: + movie_count = tryInt(data['data']['movie_count']) + + if movie_count == 0: + log.debug('%s - found no results', (self.getName())) + else: + + movie_results = data['data']['movies'] + for i in range(0,len(movie_results)): + result = data['data']['movies'][i] + name = result['title'] + + t = movie['info']['original_title'].split(' ') + + if all(word in name for word in t) and movie['info']['year'] == result['year']: + + year = result['year'] + detail_url = result['url'] + + for torrent in result['torrents']: + t_quality = torrent['quality'] + + if t_quality in quality['label']: + hash = torrent['hash'] + size = tryInt(torrent['size_bytes'] / 1048576) + seeders = tryInt(torrent['seeds']) + leechers = tryInt(torrent['peers']) + pubdate = torrent['date_uploaded'] # format: 2017-02-17 18:40:03 + pubdate = datetime.strptime(pubdate, '%Y-%m-%d %H:%M:%S') + age = (datetime.now() - pubdate).days + + results.append({ + 'id': random.randint(100, 9999), + 'name': '%s (%s) %s %s %s' % (name, year, 'YTS', t_quality, 'BR-Rip'), + 'url': self.make_magnet(hash, name), + 'size': size, + 'seeders': seeders, + 'leechers': leechers, + 'age': age, + 'detail_url': detail_url, + 'score': 1 + }) + + return + + def make_magnet(self, hash, name): + url_encoded_trackers = 'udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce&tr=%0Audp%3A%2F%2Ftracker.openbittorr' \ + 'ent.com%3A80&tr=%0Audp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=%0Audp%3A%2F%2Fglot' \ + 'orrents.pw%3A6969%2Fannounce&tr=%0Audp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannou' \ + 'nce&tr=%0Audp%3A%2F%2Ftorrent.gresille.org%3A80%2Fannounce&tr=%0Audp%3A%2F%2Fp4p.are' \ + 'nabg.com%3A1337&tr=%0Audp%3A%2F%2Ftracker.leechers-paradise.org%3A6969]' + + return 'magnet:?xt=urn:btih:%s&dn=%s&tr=%s' % (hash, name.replace(' ', '+'), url_encoded_trackers) + + +config = [{ + 'name': 'yts', + 'groups': [ + { + 'tab': 'searcher', + 'list': 'torrent_providers', + 'name': 'YTS', + 'description': 'YTS', + 'wizard': True, + 'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACL0lEQVR4AS1SPW/UQBAd23fxne/Ld2dvzvHuzPocEBAKokCBqG' + 'iQ6IgACYmvUKRBFEQgKKGg4BAlUoggggYUEQpSHOI7CIEoQs/fYcbLaU/efTvvvZlnA1qydoxU5kcxX0CkgmQZtPy0hCUjvK+W' + 'gEByOZ5dns1O5bzna8fRVkgsxH8B0YouIvBhdD5T11NiVOoKrsttyUcpRW0InUrFnwe9HzuP2uaQZYhF2LQ76TTXw2RVMTK8mY' + 'Ybjfh+zNquMVCrqn93aArLSixPxnafdGDLaz1tjY5rmNa8z5BczEQOxQfCl1GyoqoWxYRN1bkh7ELw3q/vhP6HIL4TG9Kumpjg' + 'vwuyM7OsjSj98E/vszMfZ7xvPtMaWxGO5crwIumKCR5HxDtJ0AWKGG204RfUd/3smJYqwem/Q7BTS1ZGfM4LNpVwuKAz6cMeRO' + 'st0S2EwNE7GjTehO2H3dxqIpdkydat15G3F8SXBi4GlpBNlSz012L/k2+W0CLLk/jbcf13rf41yJeMQ8QWUZiHCfCA9ad+81nE' + 'KPtoS9mJOf9v0NmMJHgUT6xayheK9EIK7JJeU/AF4scDF7Y5SPlJrRcxJ+um4ibNEdObxLiIwJim+eT2AL5D9CIcnZ5zvSJi9e' + 'IlNHVVtZ831dk5svPgvjPWTq+ktWkd/kD0qtm71x+sDQe3kt6DXnM7Ct+GajmTxKlkAokWljyAKSm5oWa2w+BH4P2UuVub7eTy' + 'iGOQYapY/wEztHduSDYz5gAAAABJRU5ErkJggg==', + + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + 'default': False + }, + { + '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': 'info', + 'label': 'Info', + 'type':'bool', + 'default':'False', + 'description': 'YTS will only work if you set the minimum size for 720p to 500 and 1080p to 800', + }, + { + 'name': 'extra_score', + 'advanced': True, + 'label': 'Extra Score', + 'type': 'int', + 'default': 0, + 'description': 'Starting score for each release found via this provider.', + } + ], + } + ] +}] diff --git a/couchpotato/core/media/movie/providers/automation/base.py b/couchpotato/core/media/movie/providers/automation/base.py index e606e7f..ee19649 100644 --- a/couchpotato/core/media/movie/providers/automation/base.py +++ b/couchpotato/core/media/movie/providers/automation/base.py @@ -1,4 +1,5 @@ import time +import unicodedata from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.logger import CPLog @@ -45,7 +46,11 @@ class Automation(AutomationBase): def search(self, name, year = None, imdb_only = False): - cache_name = name.decode('utf-8').encode('ascii', 'ignore') + try: + cache_name = name.decode('utf-8').encode('ascii', 'ignore') + except UnicodeEncodeError: + cache_name = unicodedata.normalize('NFKD', name).encode('ascii','ignore') + prop_name = 'automation.cached.%s.%s' % (cache_name, year) cached_imdb = Env.prop(prop_name, default = False) if cached_imdb and imdb_only: diff --git a/couchpotato/core/media/movie/providers/nzb/nzbclub.py b/couchpotato/core/media/movie/providers/nzb/nzbclub.py index 2a43ba2..8713106 100644 --- a/couchpotato/core/media/movie/providers/nzb/nzbclub.py +++ b/couchpotato/core/media/movie/providers/nzb/nzbclub.py @@ -14,7 +14,7 @@ class NZBClub(MovieProvider, Base): def buildUrl(self, media): q = tryUrlencode({ - 'q': '"%s"' % fireEvent('library.query', media, single = True), + 'q': '%s' % fireEvent('library.query', media, single = True), }) query = tryUrlencode({ diff --git a/couchpotato/core/media/movie/providers/torrent/iptorrents.py b/couchpotato/core/media/movie/providers/torrent/iptorrents.py index 68365b8..fd83a0a 100644 --- a/couchpotato/core/media/movie/providers/torrent/iptorrents.py +++ b/couchpotato/core/media/movie/providers/torrent/iptorrents.py @@ -11,12 +11,12 @@ class IPTorrents(MovieProvider, Base): cat_ids = [ ([87], ['3d']), - ([48], ['720p', '1080p']), ([89], ['bd50']), - ([96], ['cam', 'ts', 'tc', 'r5', 'scr']), - ([48, 20, 90], ['brrip']), + ([48], ['720p', '1080p']), + ([48, 20], ['brrip']), ([7, 77], ['dvdrip']), - ([6], ['dvdr']) + ([6], ['dvdr']), + ([96], ['cam', 'ts', 'tc', 'r5', 'scr']), ] def buildUrl(self, title, media, quality): diff --git a/couchpotato/core/media/movie/providers/torrent/passthepopcorn.py b/couchpotato/core/media/movie/providers/torrent/passthepopcorn.py index 2b577ad..5e92eef 100644 --- a/couchpotato/core/media/movie/providers/torrent/passthepopcorn.py +++ b/couchpotato/core/media/movie/providers/torrent/passthepopcorn.py @@ -10,6 +10,7 @@ autoload = 'PassThePopcorn' class PassThePopcorn(MovieProvider, Base): quality_search_params = { + '2160p': {'resolution': '4K'}, 'bd50': {'media': 'Blu-ray', 'format': 'BD50'}, '1080p': {'resolution': '1080p'}, '720p': {'resolution': '720p'}, @@ -24,6 +25,7 @@ class PassThePopcorn(MovieProvider, Base): } post_search_filters = { + '2160p': {'Resolution': ['4K']}, 'bd50': {'Codec': ['BD50']}, '1080p': {'Resolution': ['1080p']}, '720p': {'Resolution': ['720p']}, diff --git a/couchpotato/core/media/movie/providers/torrent/yts.py b/couchpotato/core/media/movie/providers/torrent/yts.py new file mode 100644 index 0000000..c20117a --- /dev/null +++ b/couchpotato/core/media/movie/providers/torrent/yts.py @@ -0,0 +1,10 @@ +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.providers.torrent.yts import Base +from couchpotato.core.media.movie.providers.base import MovieProvider + +log = CPLog(__name__) + +autoload = 'Yts' + +class Yts(MovieProvider, Base): + pass diff --git a/couchpotato/core/notifications/discord.py b/couchpotato/core/notifications/discord.py new file mode 100644 index 0000000..829ff26 --- /dev/null +++ b/couchpotato/core/notifications/discord.py @@ -0,0 +1,93 @@ +from couchpotato.core.logger import CPLog +from couchpotato.core.notifications.base import Notification +import json +import requests + +log = CPLog(__name__) +autoload = 'Discord' + + +class Discord(Notification): + required_confs = ('webhook_url',) + + def notify(self, message='', data=None, listener=None): + for key in self.required_confs: + if not self.conf(key): + log.warning('Discord notifications are enabled, but ' + '"{0}" is not specified.'.format(key)) + return False + + data = data or {} + message = message.strip() + + if self.conf('include_imdb') and 'identifier' in data: + template = ' http://www.imdb.com/title/{0[identifier]}/' + message += template.format(data) + + headers = {b"Content-Type": b"application/json"} + try: + r = requests.post(self.conf('webhook_url'), data=json.dumps(dict(content=message, username=self.conf('bot_name'), avatar_url=self.conf('avatar_url'), tts=self.conf('discord_tts'))), headers=headers) + r.status_code + except Exception as e: + log.warning('Error Sending Discord response error code: {0}'.format(r.status_code)) + return False + return True + + +config = [{ + 'name': 'discord', + 'groups': [ + { + 'tab': 'notifications', + 'list': 'notification_providers', + 'name': 'discord', + 'options': [ + { + 'name': 'enabled', + 'default': 0, + 'type': 'enabler', + }, + { + 'name': 'webhook_url', + 'description': ( + 'Your Discord authentication webhook URL.', + 'Created under channel settings.' + ) + }, + { + 'name': 'include_imdb', + 'default': True, + 'type': 'bool', + 'descrpition': 'Include a link to the movie page on IMDB.' + }, + { + 'name': 'bot_name', + 'description': 'Name of bot.', + 'default': 'CouchPotato', + 'advanced': True, + }, + { + 'name': 'avatar_url', + 'description': 'URL to an image to use as the avatar for ' + 'notifications.', + 'default': 'https://couchpota.to/media/images/couch.png', + 'advanced': True, + }, + { + 'name': 'discord_tts', + 'default': 0, + 'type': 'bool', + 'advanced': True, + 'description': 'Send notification using text-to-speech.', + }, + { + 'name': 'on_snatch', + 'default': 0, + 'type': 'bool', + 'advanced': True, + 'description': 'Also send message when movie is snatched.', + }, + ], + } + ], +}] diff --git a/couchpotato/core/notifications/plex/server.py b/couchpotato/core/notifications/plex/server.py index 956c66c..9c8df76 100644 --- a/couchpotato/core/notifications/plex/server.py +++ b/couchpotato/core/notifications/plex/server.py @@ -51,7 +51,8 @@ class PlexServer(object): req = urllib2.Request("https://plex.tv/users/sign_in.xml", data="") authheader = "Basic %s" % base64.encodestring('%s:%s' % (username, password))[:-1] req.add_header("Authorization", authheader) - req.add_header("X-Plex-Product", "Couchpotato Notifier") + req.add_header("X-Plex-Device-Name", "CouchPotato") + req.add_header("X-Plex-Product", "CouchPotato Notifier") req.add_header("X-Plex-Client-Identifier", "b3a6b24dcab2224bdb101fc6aa08ea5e2f3147d6") req.add_header("X-Plex-Version", "1.0") diff --git a/couchpotato/core/notifications/script.py b/couchpotato/core/notifications/script.py new file mode 100644 index 0000000..9d8f753 --- /dev/null +++ b/couchpotato/core/notifications/script.py @@ -0,0 +1,58 @@ +import traceback +import subprocess + +from couchpotato.core.helpers.encoding import toUnicode +from couchpotato.core.helpers.variable import getIdentifier +from couchpotato.core.logger import CPLog +from couchpotato.core.notifications.base import Notification + + +log = CPLog(__name__) + +autoload = 'Script' + +class Script(Notification): + + def notify(self, message = '', data = None, listener = None): + if not data: data = {} + + script_data = { + 'message': toUnicode(message) + } + + if getIdentifier(data): + script_data.update({ + 'imdb_id': getIdentifier(data) + }) + + try: + subprocess.call([self.conf('path'), message]) + return True + except: + log.error('Script notification failed: %s', traceback.format_exc()) + + return False + + +config = [{ + 'name': 'script', + 'groups': [ + { + 'tab': 'notifications', + 'list': 'notification_providers', + 'name': 'script', + 'label': 'Script', + 'options': [ + { + 'name': 'enabled', + 'default': 0, + 'type': 'enabler', + }, + { + 'name': 'path', + 'description': 'The path to the script to execute.' + } + ] + } + ] +}] diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index f2cd100..a55eb06 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -114,7 +114,12 @@ class QualityPlugin(Plugin): db = get_db() quality_dict = {} - quality = db.get('quality', identifier, with_doc = True)['doc'] + try: + quality = db.get('quality', identifier, with_doc = True)['doc'] + except RecordNotFound: + log.error("Unable to find '%s' in the quality DB", indentifier) + quality = None + if quality: quality_dict = mergeDicts(self.getQuality(quality['identifier']), quality) diff --git a/couchpotato/core/plugins/quality/static/quality.js b/couchpotato/core/plugins/quality/static/quality.js index 60a2449..d9aaf58 100644 --- a/couchpotato/core/plugins/quality/static/quality.js +++ b/couchpotato/core/plugins/quality/static/quality.js @@ -31,9 +31,9 @@ var QualityBase = new Class({ getQuality: function(identifier){ try { - return this.qualities.filter(function(q){ + return (this.qualities.filter(function(q){ return q.identifier == identifier; - }).pick(); + }).pick() || {}); } catch(e){} diff --git a/couchpotato/core/plugins/renamer.py b/couchpotato/core/plugins/renamer.py index 6780c0e..4163383 100755 --- a/couchpotato/core/plugins/renamer.py +++ b/couchpotato/core/plugins/renamer.py @@ -216,6 +216,9 @@ class Renamer(Plugin): except: log.error('Failed getting files from %s: %s', (media_folder, traceback.format_exc())) + # post_filter files from configuration; this is a ":"-separated list of globs + files = self.filesAfterIgnoring(files) + db = get_db() # Extend the download info with info stored in the downloaded release @@ -347,11 +350,22 @@ class Renamer(Plugin): 'category': category_label, '3d': '3D' if group['meta_data']['quality'].get('is_3d', 0) else '', '3d_type': group['meta_data'].get('3d_type'), + '3d_type_short': group['meta_data'].get('3d_type'), } if replacements['mpaa_only'] not in ('G', 'PG', 'PG-13', 'R', 'NC-17'): replacements['mpaa_only'] = 'Not Rated' + if replacements['3d_type_short']: + replacements['3d_type_short'] = replacements['3d_type_short'].replace('Half ', 'H').replace('Full ', '') + if self.conf('use_tab_threed') and replacements['3d_type']: + if 'OU' in replacements['3d_type']: + replacements['3d_type'] = replacements['3d_type'].replace('OU','TAB') + if self.conf('use_tab_threed') and replacements['3d_type_short']: + if 'OU' in replacements['3d_type_short']: + replacements['3d_type_short'] = replacements['3d_type_short'].replace('OU','TAB') + + for file_type in group['files']: # Move nfo depending on settings @@ -1165,6 +1179,30 @@ Remove it if you want it to be renamed (again, or at least let it try again) def movieInFromFolder(self, media_folder): return media_folder and isSubFolder(media_folder, sp(self.conf('from'))) or not media_folder + @property + def ignored_in_path(self): + return self.conf('ignored_in_path').split(":") if self.conf('ignored_in_path') else [] + + def filesAfterIgnoring(self, original_file_list): + kept_files = [] + for path in original_file_list: + if self.keepFile(path): + kept_files.append(path) + else: + log.debug('Ignored "%s" during renaming', path) + return kept_files + + def keepFile(self, filename): + + # ignoredpaths + for i in self.ignored_in_path: + if i in filename.lower(): + log.debug('Ignored "%s" contains "%s".', (filename, i)) + return False + + # All is OK + return True + def extractFiles(self, folder = None, media_folder = None, files = None, cleanup = False): if not files: files = [] @@ -1298,6 +1336,7 @@ rename_options = { 'quality_type': '(HD) or (SD)', '3d': '3D', '3d_type': '3D Type (Full SBS)', + '3d_type_short' : 'Short 3D Type (FSBS)', 'video': 'Video (x264)', 'audio': 'Audio (DTS)', 'group': 'Releasegroup name', @@ -1360,6 +1399,14 @@ config = [{ }, { 'advanced': True, + 'name': 'use_tab_threed', + 'type': 'bool', + 'label': 'Use TAB 3D', + 'description': ('Use TAB (Top And Bottom) instead of OU (Over Under).','This will allow Kodi to recognize vertical formatted 3D movies properly.'), + 'default': True + }, + { + 'advanced': True, 'name': 'replace_doubles', 'type': 'bool', 'label': 'Clean Name', @@ -1367,6 +1414,12 @@ config = [{ 'default': True }, { + 'name': 'ignored_in_path', + 'label': 'Ignored file patterns', + 'description': ('A list of globs to path match when scanning, separated by ":"', 'anything on this list will be skipped during rename operations'), + 'default': '*/.sync/*', + }, + { 'name': 'unrar', 'type': 'bool', 'description': 'Extract rar files if found.', diff --git a/couchpotato/core/plugins/scanner.py b/couchpotato/core/plugins/scanner.py index 9bcb73f..9233d28 100644 --- a/couchpotato/core/plugins/scanner.py +++ b/couchpotato/core/plugins/scanner.py @@ -28,6 +28,7 @@ class Scanner(Plugin): '_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo', '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'] + ignored_extensions = ['ignore', 'lftp-pget-status'] extensions = { 'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v', 'flv'], 'movie_extra': ['mds'], @@ -42,9 +43,9 @@ class Scanner(Plugin): 'Half SBS': [('half', 'sbs'), ('h', 'sbs'), 'hsbs'], 'Full SBS': [('full', 'sbs'), ('f', 'sbs'), 'fsbs'], 'SBS': ['sbs'], - 'Half OU': [('half', 'ou'), ('h', 'ou'), 'hou'], - 'Full OU': [('full', 'ou'), ('h', 'ou'), 'fou'], - 'OU': ['ou'], + 'Half OU': [('half', 'ou'), ('h', 'ou'), ('half', 'tab'), ('h', 'tab'), 'htab', 'hou'], + 'Full OU': [('full', 'ou'), ('f', 'ou'), ('full', 'tab'), ('f', 'tab'), 'ftab', 'fou'], + 'OU': ['ou', 'tab'], 'Frame Packed': ['mvc', ('complete', 'bluray')], '3D': ['3d'] } @@ -225,12 +226,12 @@ class Scanner(Plugin): group['unsorted_files'].extend(found_files) leftovers = leftovers - found_files - has_ignored += 1 if ext == 'ignore' else 0 + has_ignored += 1 if ext in self.ignored_extensions else 0 if has_ignored == 0: for file_path in list(group['unsorted_files']): ext = getExt(file_path) - has_ignored += 1 if ext == 'ignore' else 0 + has_ignored += 1 if ext in self.ignored_extensions else 0 if has_ignored > 0: ignored_identifiers.append(identifier) diff --git a/couchpotato/templates/login.html b/couchpotato/templates/login.html index ef9a8ec..63c7b59 100644 --- a/couchpotato/templates/login.html +++ b/couchpotato/templates/login.html @@ -25,7 +25,7 @@

CouchPotato

-
+