diff --git a/couchpotato/core/media/_base/library/main.py b/couchpotato/core/media/_base/library/main.py index a723de50..b90f4c38 100644 --- a/couchpotato/core/media/_base/library/main.py +++ b/couchpotato/core/media/_base/library/main.py @@ -1,10 +1,36 @@ +from couchpotato import get_db +from couchpotato.api import addApiView from couchpotato.core.event import addEvent, fireEvent +from couchpotato.core.logger import CPLog from couchpotato.core.media._base.library.base import LibraryBase +log = CPLog(__name__) + class Library(LibraryBase): def __init__(self): addEvent('library.title', self.title) + addEvent('library.related', self.related) + addEvent('library.root', self.root) + + addApiView('library.query', self.queryView) + addApiView('library.related', self.relatedView) + + def queryView(self, media_id, **kwargs): + db = get_db() + media = db.get('id', media_id) + + return { + 'result': fireEvent('library.query', media, single = True) + } + + def relatedView(self, media_id, **kwargs): + db = get_db() + media = db.get('id', media_id) + + return { + 'result': fireEvent('library.related', media, single = True) + } def title(self, library): return fireEvent( @@ -16,3 +42,25 @@ class Library(LibraryBase): include_identifier = False, single = True ) + + def related(self, media): + result = {media['type']: media} + + db = get_db() + cur = media + + while cur and cur.get('parent_id'): + cur = db.get('id', cur['parent_id']) + + result[cur['type']] = cur + + return result + + def root(self, media): + db = get_db() + cur = media + + while cur and cur.get('parent_id'): + cur = db.get('id', cur['parent_id']) + + return cur diff --git a/couchpotato/core/media/_base/matcher/main.py b/couchpotato/core/media/_base/matcher/main.py index 2034249b..64e13ae6 100644 --- a/couchpotato/core/media/_base/matcher/main.py +++ b/couchpotato/core/media/_base/matcher/main.py @@ -40,7 +40,7 @@ class Matcher(MatcherBase): return False def correctTitle(self, chain, media): - root_library = media['library']['root_library'] + root = fireEvent('library.root', media, single = True) if 'show_name' not in chain.info or not len(chain.info['show_name']): log.info('Wrong: missing show name in parsed result') @@ -50,10 +50,10 @@ class Matcher(MatcherBase): chain_words = [x.lower() for x in chain.info['show_name']] # Build a list of possible titles of the media we are searching for - titles = root_library['info']['titles'] + titles = root['info']['titles'] # Add year suffix titles (will result in ['', ' ', '', ...]) - suffixes = [None, root_library['info']['year']] + suffixes = [None, root['info']['year']] titles = [ title + ((' %s' % suffix) if suffix else '') diff --git a/couchpotato/core/media/show/__init__.py b/couchpotato/core/media/show/__init__.py index 89af436c..89bfef69 100644 --- a/couchpotato/core/media/show/__init__.py +++ b/couchpotato/core/media/show/__init__.py @@ -2,5 +2,10 @@ from couchpotato.core.media import MediaBase class ShowTypeBase(MediaBase): - _type = 'show' + + def getType(self): + if hasattr(self, 'type') and self.type != self._type: + return '%s.%s' % (self._type, self.type) + + return self._type diff --git a/couchpotato/core/media/show/_base/main.py b/couchpotato/core/media/show/_base/main.py index 7d7e637b..f96ff61f 100644 --- a/couchpotato/core/media/show/_base/main.py +++ b/couchpotato/core/media/show/_base/main.py @@ -17,7 +17,6 @@ log = CPLog(__name__) class ShowBase(MediaBase): _type = 'show' - query_condenser = QueryCondenser() def __init__(self): super(ShowBase, self).__init__() @@ -35,8 +34,6 @@ class ShowBase(MediaBase): addEvent('show.add', self.add) addEvent('show.update_info', self.updateInfo) - addEvent('media.search_query', self.query) - def addView(self, **kwargs): add_dict = self.add(params = kwargs) @@ -255,25 +252,3 @@ class ShowBase(MediaBase): log.error('Failed update media: %s', traceback.format_exc()) return {} - - def query(self, media, first = True, condense = True, **kwargs): - if media.get('type') != 'show': - return - - titles = media['info']['titles'] - - if condense: - # Use QueryCondenser to build a list of optimal search titles - condensed_titles = self.query_condenser.distinct(titles) - - if condensed_titles: - # Use condensed titles if we got a valid result - titles = condensed_titles - else: - # Fallback to simplifying titles - titles = [simplifyString(title) for title in titles] - - if first: - return titles[0] if titles else None - - return titles diff --git a/couchpotato/core/media/show/episode.py b/couchpotato/core/media/show/episode.py index c4a0a1a5..0f05f284 100644 --- a/couchpotato/core/media/show/episode.py +++ b/couchpotato/core/media/show/episode.py @@ -13,9 +13,6 @@ autoload = 'Episode' class Episode(MediaBase): def __init__(self): - addEvent('media.search_query', self.query) - addEvent('media.identifier', self.identifier) - addEvent('show.episode.add', self.add) addEvent('show.episode.update_info', self.updateInfo) @@ -85,63 +82,3 @@ class Episode(MediaBase): self.getPoster(image_urls, existing_files) return episode - - def query(self, library, first = True, condense = True, include_identifier = True, **kwargs): - if library is list or library.get('type') != 'episode': - return - - # Get the titles of the season - if not library.get('related_libraries', {}).get('season', []): - log.warning('Invalid library, unable to determine title.') - return - - titles = fireEvent( - 'media.search_query', - library['related_libraries']['season'][0], - first=False, - include_identifier=include_identifier, - condense=condense, - - single=True - ) - - identifier = fireEvent('media.identifier', library, single = True) - - # Add episode identifier to titles - if include_identifier and identifier.get('episode'): - titles = [title + ('E%02d' % identifier['episode']) for title in titles] - - - if first: - return titles[0] if titles else None - - return titles - - - def identifier(self, media): - if media.get('type') != 'episode': - return - - identifier = { - 'season': None, - 'episode': None - } - - scene_map = media['info'].get('map_episode', {}).get('scene') - - if scene_map: - # Use scene mappings if they are available - identifier['season'] = scene_map.get('season_nr') - identifier['episode'] = scene_map.get('episode_nr') - else: - # Fallback to normal season/episode numbers - identifier['season'] = media['info'].get('season_number') - identifier['episode'] = media['info'].get('number') - - - # Cast identifiers to integers - # TODO this will need changing to support identifiers with trailing 'a', 'b' characters - identifier['season'] = tryInt(identifier['season'], None) - identifier['episode'] = tryInt(identifier['episode'], None) - - return identifier diff --git a/couchpotato/core/media/show/library/__init__.py b/couchpotato/core/media/show/library/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/couchpotato/core/media/show/library/episode.py b/couchpotato/core/media/show/library/episode.py new file mode 100644 index 00000000..7161f2d2 --- /dev/null +++ b/couchpotato/core/media/show/library/episode.py @@ -0,0 +1,71 @@ +from couchpotato.core.event import addEvent, fireEvent +from couchpotato.core.helpers.variable import tryInt +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.library.base import LibraryBase + +log = CPLog(__name__) + +autoload = 'EpisodeLibraryPlugin' + + +class EpisodeLibraryPlugin(LibraryBase): + def __init__(self): + addEvent('library.query', self.query) + addEvent('library.identifier', self.identifier) + + def query(self, media, first = True, condense = True, include_identifier = True, **kwargs): + if media.get('type') != 'episode': + return + + related = fireEvent('library.related', media, single = True) + + # Get season titles + titles = fireEvent( + 'library.query', related['season'], + + first = False, + include_identifier = include_identifier, + condense = condense, + + single = True + ) + + # Add episode identifier to titles + if include_identifier: + identifier = fireEvent('library.identifier', media, single = True) + + if identifier and identifier.get('episode'): + titles = [title + ('E%02d' % identifier['episode']) for title in titles] + + if first: + return titles[0] if titles else None + + return titles + + def identifier(self, media): + if media.get('type') != 'episode': + return + + identifier = { + 'season': None, + 'episode': None + } + + # TODO identifier mapping + # scene_map = media['info'].get('map_episode', {}).get('scene') + + # if scene_map: + # # Use scene mappings if they are available + # identifier['season'] = scene_map.get('season_nr') + # identifier['episode'] = scene_map.get('episode_nr') + # else: + # Fallback to normal season/episode numbers + identifier['season'] = media['info'].get('season_number') + identifier['episode'] = media['info'].get('number') + + # Cast identifiers to integers + # TODO this will need changing to support identifiers with trailing 'a', 'b' characters + identifier['season'] = tryInt(identifier['season'], None) + identifier['episode'] = tryInt(identifier['episode'], None) + + return identifier diff --git a/couchpotato/core/media/show/library/season.py b/couchpotato/core/media/show/library/season.py new file mode 100644 index 00000000..228d1432 --- /dev/null +++ b/couchpotato/core/media/show/library/season.py @@ -0,0 +1,52 @@ +from couchpotato.core.event import addEvent, fireEvent +from couchpotato.core.helpers.variable import tryInt +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.library.base import LibraryBase + +log = CPLog(__name__) + +autoload = 'SeasonLibraryPlugin' + + +class SeasonLibraryPlugin(LibraryBase): + def __init__(self): + addEvent('library.query', self.query) + addEvent('library.identifier', self.identifier) + + def query(self, media, first = True, condense = True, include_identifier = True, **kwargs): + if media.get('type') != 'season': + return + + related = fireEvent('library.related', media, single = True) + + # Get show titles + titles = fireEvent( + 'library.query', related['show'], + + first = False, + condense = condense, + + single = True + ) + + # TODO map_names + + # Add season identifier to titles + if include_identifier: + identifier = fireEvent('library.identifier', media, single = True) + + if identifier and identifier.get('season') is not None: + titles = [title + (' S%02d' % identifier['season']) for title in titles] + + if first: + return titles[0] if titles else None + + return titles + + def identifier(self, media): + if media.get('type') != 'season': + return + + return { + 'season': tryInt(media['info']['number'], None) + } diff --git a/couchpotato/core/media/show/library/show.py b/couchpotato/core/media/show/library/show.py new file mode 100644 index 00000000..168804ca --- /dev/null +++ b/couchpotato/core/media/show/library/show.py @@ -0,0 +1,38 @@ +from couchpotato.core.event import addEvent +from couchpotato.core.helpers.encoding import simplifyString +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.library.base import LibraryBase +from qcond import QueryCondenser + +log = CPLog(__name__) + +autoload = 'ShowLibraryPlugin' + + +class ShowLibraryPlugin(LibraryBase): + query_condenser = QueryCondenser() + + def __init__(self): + addEvent('library.query', self.query) + + def query(self, media, first = True, condense = True, include_identifier = True, **kwargs): + if media.get('type') != 'show': + return + + titles = media['info']['titles'] + + if condense: + # Use QueryCondenser to build a list of optimal search titles + condensed_titles = self.query_condenser.distinct(titles) + + if condensed_titles: + # Use condensed titles if we got a valid result + titles = condensed_titles + else: + # Fallback to simplifying titles + titles = [simplifyString(title) for title in titles] + + if first: + return titles[0] if titles else None + + return titles diff --git a/couchpotato/core/media/show/matcher.py b/couchpotato/core/media/show/matcher.py index 4056d64e..7df36d10 100644 --- a/couchpotato/core/media/show/matcher.py +++ b/couchpotato/core/media/show/matcher.py @@ -96,7 +96,7 @@ class Episode(Base): log.info2('Wrong: releases with identifier ranges are not supported yet') return False - required = fireEvent('media.identifier', media['library'], single = True) + required = fireEvent('library.identifier', media, single = True) # TODO - Support air by date episodes # TODO - Support episode parts diff --git a/couchpotato/core/media/show/searcher.py b/couchpotato/core/media/show/searcher.py deleted file mode 100644 index 255d12d0..00000000 --- a/couchpotato/core/media/show/searcher.py +++ /dev/null @@ -1,232 +0,0 @@ -import time -from couchpotato import Env, get_db -from couchpotato.core.event import addEvent, fireEvent -from couchpotato.core.helpers.variable import getTitle, toIterable -from couchpotato.core.logger import CPLog -from couchpotato.core.media._base.searcher.base import SearcherBase -from couchpotato.core.media._base.searcher.main import SearchSetupError -from couchpotato.core.media.show import ShowTypeBase -from qcond import QueryCondenser - -log = CPLog(__name__) - -autoload = 'ShowSearcher' - - -class ShowSearcher(SearcherBase, ShowTypeBase): - - type = ['show', 'season', 'episode'] - - in_progress = False - - def __init__(self): - super(ShowSearcher, self).__init__() - - self.query_condenser = QueryCondenser() - - addEvent('season.searcher.single', self.singleSeason) - addEvent('episode.searcher.single', self.singleEpisode) - - addEvent('searcher.correct_release', self.correctRelease) - addEvent('searcher.get_search_title', self.getSearchTitle) - - - - def single(self, media, search_protocols = None, manual = False): - - # Find out search type - try: - if not search_protocols: - search_protocols = fireEvent('searcher.protocols', single = True) - except SearchSetupError: - return - - if not media['profile_id'] or media['status'] == 'done': - log.debug('Show doesn\'t have a profile or already done, assuming in manage tab.') - return - - show_title = fireEvent('media.search_query', media, condense = False, single = True) - - fireEvent('notify.frontend', type = 'show.searcher.started.%s' % media['_id'], data = True, message = 'Searching for "%s"' % show_title) - - media = self.extendShow(media) - - db = get_db() - - profile = db.get('id', media['profile_id']) - quality_order = fireEvent('quality.order', single = True) - - seasons = media.get('seasons', {}) - for sx in seasons: - - # Skip specials for now TODO: set status for specials to skipped by default - if sx == 0: continue - - season = seasons.get(sx) - - # Check if full season can be downloaded TODO: add - season_success = self.singleSeason(season, media, profile) - - # Do each episode seperately - if not season_success: - episodes = season.get('episodes', {}) - for ex in episodes: - episode = episodes.get(ex) - - self.singleEpisode(episode, season, media, profile, quality_order, search_protocols) - - # TODO - return - - # TODO - return - - fireEvent('notify.frontend', type = 'show.searcher.ended.%s' % media['_id'], data = True) - - def singleSeason(self, media, show, profile): - - # Check if any episode is already snatched - active = 0 - episodes = media.get('episodes', {}) - for ex in episodes: - episode = episodes.get(ex) - - if episode.get('status') in ['active']: - active += 1 - - if active != len(episodes): - return False - - # Try and search for full season - # TODO: - - return False - - def singleEpisode(self, media, season, show, profile, quality_order, search_protocols = None, manual = False): - - - # TODO: check episode status - - - # TODO: check air date - #if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): - # too_early_to_search.append(quality_type['quality']['identifier']) - # return - - ret = False - has_better_quality = None - found_releases = [] - too_early_to_search = [] - - releases = fireEvent('release.for_media', media['_id'], single = True) - show_title = getTitle(show) - episode_identifier = '%s S%02d%s' % (show_title, season['info'].get('number', 0), "E%02d" % media['info'].get('number')) - - # Add parents - media['show'] = show - media['season'] = season - - index = 0 - for q_identifier in profile.get('qualities'): - quality_custom = { - 'quality': q_identifier, - 'finish': profile['finish'][index], - 'wait_for': profile['wait_for'][index], - '3d': profile['3d'][index] if profile.get('3d') else False - } - - has_better_quality = 0 - - # See if better quality is available - for release in releases: - if quality_order.index(release['quality']) <= quality_order.index(q_identifier) and release['status'] not in ['available', 'ignored', 'failed']: - has_better_quality += 1 - - # Don't search for quality lower then already available. - if has_better_quality is 0: - - log.info('Searching for %s in %s', (episode_identifier, q_identifier)) - quality = fireEvent('quality.single', identifier = q_identifier, single = True) - quality['custom'] = quality_custom - - results = fireEvent('searcher.search', search_protocols, media, quality, single = True) - if len(results) == 0: - log.debug('Nothing found for %s in %s', (episode_identifier, q_identifier)) - - # Add them to this movie releases list - found_releases += fireEvent('release.create_from_search', results, media, quality, single = True) - - # Try find a valid result and download it - if fireEvent('release.try_download_result', results, media, quality, manual, single = True): - ret = True - - # Remove releases that aren't found anymore - for release in releases: - if release.get('status') == 'available' and release.get('identifier') not in found_releases: - fireEvent('release.delete', release.get('id'), single = True) - else: - log.info('Better quality (%s) already available or snatched for %s', (q_identifier, episode_identifier)) - fireEvent('media.restatus', media['_id']) - break - - # Break if CP wants to shut down - if self.shuttingDown() or ret: - break - - if len(too_early_to_search) > 0: - log.info2('Too early to search for %s, %s', (too_early_to_search, episode_identifier)) - - def correctRelease(self, release = None, media = None, quality = None, **kwargs): - - if media.get('type') not in ['season', 'episode']: return - - retention = Env.setting('retention', section = 'nzb') - - if release.get('seeders') is None and 0 < retention < release.get('age', 0): - log.info2('Wrong: Outside retention, age is %s, needs %s or lower: %s', (release['age'], retention, release['name'])) - return False - - # Check for required and ignored words - if not fireEvent('searcher.correct_words', release['name'], media, single = True): - return False - - # TODO Matching is quite costly, maybe we should be caching release matches somehow? (also look at caper optimizations) - match = fireEvent('matcher.match', release, media, quality, single = True) - if match: - return match.weight - - return False - - def extendShow(self, media): - - db = get_db() - - seasons = db.get_many('media_children', media['_id'], with_doc = True) - - media['seasons'] = {} - - for sx in seasons: - season = sx['doc'] - - # Add episode info - season['episodes'] = {} - episodes = db.get_many('media_children', sx['_id'], with_doc = True) - - for se in episodes: - episode = se['doc'] - season['episodes'][episode['info'].get('number')] = episode - - # Add season to show - media['seasons'][season['info'].get('number', 0)] = season - - return media - - def searchAll(self): - pass - - def getSearchTitle(self, media): - # TODO: this should be done for season and episode - if media['type'] == 'season': - return getTitle(media) - elif media['type'] == 'episode': - return getTitle(media) diff --git a/couchpotato/core/media/show/searcher/__init__.py b/couchpotato/core/media/show/searcher/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/couchpotato/core/media/show/searcher/episode.py b/couchpotato/core/media/show/searcher/episode.py new file mode 100644 index 00000000..1e6b253c --- /dev/null +++ b/couchpotato/core/media/show/searcher/episode.py @@ -0,0 +1,137 @@ +from couchpotato import fireEvent, get_db, Env +from couchpotato.api import addApiView +from couchpotato.core.event import addEvent +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.searcher.base import SearcherBase +from couchpotato.core.media._base.searcher.main import SearchSetupError +from couchpotato.core.media.show import ShowTypeBase + +log = CPLog(__name__) + +autoload = 'EpisodeSearcher' + + +class EpisodeSearcher(SearcherBase, ShowTypeBase): + type = 'episode' + + in_progress = False + + def __init__(self): + super(EpisodeSearcher, self).__init__() + + addEvent('%s.searcher.single' % self.getType(), self.single) + addEvent('searcher.correct_release', self.correctRelease) + + addApiView('%s.searcher.single' % self.getType(), self.singleView) + + def singleView(self, media_id, **kwargs): + db = get_db() + media = db.get('id', media_id) + + return { + 'result': fireEvent('%s.searcher.single' % self.getType(), media, single = True) + } + + def single(self, media, profile = None, quality_order = None, search_protocols = None, manual = False): + db = get_db() + + related = fireEvent('library.related', media, single = True) + + # TODO search_protocols, profile, quality_order can be moved to a base method + # Find out search type + try: + if not search_protocols: + search_protocols = fireEvent('searcher.protocols', single = True) + except SearchSetupError: + return + + if not profile and related['show']['profile_id']: + profile = db.get('id', related['show']['profile_id']) + + if not quality_order: + quality_order = fireEvent('quality.order', single = True) + + # TODO: check episode status + # TODO: check air date + #if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): + # too_early_to_search.append(quality_type['quality']['identifier']) + # return + + ret = False + has_better_quality = None + found_releases = [] + too_early_to_search = [] + + releases = fireEvent('release.for_media', media['_id'], single = True) + query = fireEvent('library.query', media, condense = False, single = True) + + index = 0 + for q_identifier in profile.get('qualities'): + quality_custom = { + 'quality': q_identifier, + 'finish': profile['finish'][index], + 'wait_for': profile['wait_for'][index], + '3d': profile['3d'][index] if profile.get('3d') else False + } + + has_better_quality = 0 + + # See if better quality is available + for release in releases: + if quality_order.index(release['quality']) <= quality_order.index(q_identifier) and release['status'] not in ['available', 'ignored', 'failed']: + has_better_quality += 1 + + # Don't search for quality lower then already available. + if has_better_quality is 0: + + log.info('Searching for %s in %s', (query, q_identifier)) + quality = fireEvent('quality.single', identifier = q_identifier, single = True) + quality['custom'] = quality_custom + + results = fireEvent('searcher.search', search_protocols, media, quality, single = True) + if len(results) == 0: + log.debug('Nothing found for %s in %s', (query, q_identifier)) + + # Add them to this movie releases list + found_releases += fireEvent('release.create_from_search', results, media, quality, single = True) + + # Try find a valid result and download it + if fireEvent('release.try_download_result', results, media, quality, single = True): + ret = True + + # Remove releases that aren't found anymore + for release in releases: + if release.get('status') == 'available' and release.get('identifier') not in found_releases: + fireEvent('release.delete', release.get('id'), single = True) + else: + log.info('Better quality (%s) already available or snatched for %s', (q_identifier, query)) + fireEvent('media.restatus', media['_id']) + break + + # Break if CP wants to shut down + if self.shuttingDown() or ret: + break + + if len(too_early_to_search) > 0: + log.info2('Too early to search for %s, %s', (too_early_to_search, query)) + + def correctRelease(self, release = None, media = None, quality = None, **kwargs): + if media.get('type') != 'episode': + return + + retention = Env.setting('retention', section = 'nzb') + + if release.get('seeders') is None and 0 < retention < release.get('age', 0): + log.info2('Wrong: Outside retention, age is %s, needs %s or lower: %s', (release['age'], retention, release['name'])) + return False + + # Check for required and ignored words + if not fireEvent('searcher.correct_words', release['name'], media, single = True): + return False + + # TODO Matching is quite costly, maybe we should be caching release matches somehow? (also look at caper optimizations) + match = fireEvent('matcher.match', release, media, quality, single = True) + if match: + return match.weight + + return False diff --git a/couchpotato/core/media/show/searcher/season.py b/couchpotato/core/media/show/searcher/season.py new file mode 100644 index 00000000..64fd2638 --- /dev/null +++ b/couchpotato/core/media/show/searcher/season.py @@ -0,0 +1,38 @@ +from couchpotato.core.event import addEvent +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.searcher.base import SearcherBase +from couchpotato.core.media.show import ShowTypeBase + +log = CPLog(__name__) + +autoload = 'SeasonSearcher' + + +class SeasonSearcher(SearcherBase, ShowTypeBase): + type = 'season' + + in_progress = False + + def __init__(self): + super(SeasonSearcher, self).__init__() + + addEvent('%s.searcher.single' % self.getType(), self.single) + + def single(self, media, show, profile): + + # Check if any episode is already snatched + active = 0 + episodes = media.get('episodes', {}) + for ex in episodes: + episode = episodes.get(ex) + + if episode.get('status') in ['active']: + active += 1 + + if active != len(episodes): + return False + + # Try and search for full season + # TODO: + + return False diff --git a/couchpotato/core/media/show/searcher/show.py b/couchpotato/core/media/show/searcher/show.py new file mode 100644 index 00000000..49fb775a --- /dev/null +++ b/couchpotato/core/media/show/searcher/show.py @@ -0,0 +1,83 @@ +from couchpotato import get_db +from couchpotato.core.event import fireEvent, addEvent +from couchpotato.core.helpers.variable import getTitle +from couchpotato.core.logger import CPLog +from couchpotato.core.media._base.searcher.base import SearcherBase +from couchpotato.core.media._base.searcher.main import SearchSetupError +from couchpotato.core.media.show import ShowTypeBase + +log = CPLog(__name__) + +autoload = 'ShowSearcher' + + +class ShowSearcher(SearcherBase, ShowTypeBase): + type = 'show' + + in_progress = False + + def __init__(self): + super(ShowSearcher, self).__init__() + + addEvent('%s.searcher.single' % self.getType(), self.single) + + addEvent('searcher.get_search_title', self.getSearchTitle) + + def single(self, media, search_protocols = None, manual = False): + # Find out search type + try: + if not search_protocols: + search_protocols = fireEvent('searcher.protocols', single = True) + except SearchSetupError: + return + + if not media['profile_id'] or media['status'] == 'done': + log.debug('Show doesn\'t have a profile or already done, assuming in manage tab.') + return + + show_title = fireEvent('media.search_query', media, condense = False, single = True) + + fireEvent('notify.frontend', type = 'show.searcher.started.%s' % media['_id'], data = True, message = 'Searching for "%s"' % show_title) + + media = self.extendShow(media) + + db = get_db() + + profile = db.get('id', media['profile_id']) + quality_order = fireEvent('quality.order', single = True) + + seasons = media.get('seasons', {}) + for sx in seasons: + + # Skip specials for now TODO: set status for specials to skipped by default + if sx == 0: continue + + season = seasons.get(sx) + + # Check if full season can be downloaded TODO: add + season_success = fireEvent('show.season.searcher.single', season, media, profile) + + # Do each episode seperately + if not season_success: + episodes = season.get('episodes', {}) + for ex in episodes: + episode = episodes.get(ex) + + fireEvent('show.episode.searcher.single', episode, season, media, profile, quality_order, search_protocols) + + # TODO + return + + # TODO + return + + fireEvent('notify.frontend', type = 'show.searcher.ended.%s' % media['_id'], data = True) + + def getSearchTitle(self, media): + if media.get('type') != 'show': + related = fireEvent('library.related', media, single = True) + show = related['show'] + else: + show = media + + return getTitle(show) diff --git a/couchpotato/core/media/show/season.py b/couchpotato/core/media/show/season.py index 9c6f8f07..a2dde780 100644 --- a/couchpotato/core/media/show/season.py +++ b/couchpotato/core/media/show/season.py @@ -13,9 +13,6 @@ autoload = 'Season' class Season(MediaBase): def __init__(self): - addEvent('media.search_query', self.query) - addEvent('media.identifier', self.identifier) - addEvent('show.season.add', self.add) addEvent('show.season.update_info', self.updateInfo) @@ -87,51 +84,3 @@ class Season(MediaBase): self.getPoster(image_urls, existing_files) return season - - def query(self, library, first = True, condense = True, include_identifier = True, **kwargs): - if library is list or library.get('type') != 'season': - return - - # Get the titles of the show - if not library.get('related_libraries', {}).get('show', []): - log.warning('Invalid library, unable to determine title.') - return - - titles = fireEvent( - 'media._search_query', - library['related_libraries']['show'][0], - first=False, - condense=condense, - - single=True - ) - - # Add season map_names if they exist - if 'map_names' in library['info']: - season_names = library['info']['map_names'].get(str(library['season_number']), {}) - - # Add titles from all locations - # TODO only add name maps from a specific location - for location, names in season_names.items(): - titles += [name for name in names if name and name not in titles] - - - identifier = fireEvent('media.identifier', library, single = True) - - # Add season identifier to titles - if include_identifier and identifier.get('season') is not None: - titles = [title + (' S%02d' % identifier['season']) for title in titles] - - - if first: - return titles[0] if titles else None - - return titles - - def identifier(self, library): - if library.get('type') != 'season': - return - - return { - 'season': tryInt(library['season_number'], None) - } diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index 63049243..3c80966c 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -32,7 +32,25 @@ class QualityPlugin(Plugin): {'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': ['r6'], 'allow': ['dvdr'], 'ext':[]}, {'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':[]}, {'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': [], 'ext':[]}, - {'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': [], 'ext':[]} + {'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': [], 'ext':[]}, + + # TODO come back to this later, think this could be handled better, this is starting to get out of hand.... + # BluRay + {'identifier': 'bluray_1080p', 'hd': True, 'size': (800, 5000), 'label': 'BluRay - 1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv']}, + {'identifier': 'bluray_720p', 'hd': True, 'size': (800, 5000), 'label': 'BluRay - 720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv']}, + # BDRip + {'identifier': 'bdrip_1080p', 'hd': True, 'size': (800, 5000), 'label': 'BDRip - 1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv']}, + {'identifier': 'bdrip_720p', 'hd': True, 'size': (800, 5000), 'label': 'BDRip - 720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv']}, + # BRRip + {'identifier': 'brrip_1080p', 'hd': True, 'size': (800, 5000), 'label': 'BRRip - 1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv']}, + {'identifier': 'brrip_720p', 'hd': True, 'size': (800, 5000), 'label': 'BRRip - 720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv']}, + # WEB-DL + {'identifier': 'webdl_1080p', 'hd': True, 'size': (800, 5000), 'label': 'WEB-DL - 1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv']}, + {'identifier': 'webdl_720p', 'hd': True, 'size': (800, 5000), 'label': 'WEB-DL - 720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv']}, + {'identifier': 'webdl_480p', 'hd': True, 'size': (100, 5000), 'label': 'WEB-DL - 480p', 'width': 720, 'alternative': [], 'allow': [], 'ext':['mkv']}, + # HDTV + {'identifier': 'hdtv_720p', 'hd': True, 'size': (800, 5000), 'label': 'HDTV - 720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv']}, + {'identifier': 'hdtv_sd', 'hd': False, 'size': (100, 1000), 'label': 'HDTV - SD', 'width': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'mp4', 'avi']}, ] pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr'] threed_tags = { diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py index 08f02480..7f389f2b 100644 --- a/couchpotato/core/plugins/release/main.py +++ b/couchpotato/core/plugins/release/main.py @@ -315,7 +315,7 @@ class Release(Plugin): rls['download_info'] = download_result db.update(rls) - log_movie = '%s (%s) in %s' % (getTitle(media), media['info']['year'], rls['quality']) + log_movie = '%s (%s) in %s' % (getTitle(media), media['info'].get('year'), rls['quality']) snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie) log.info(snatch_message) fireEvent('%s.snatched' % data['type'], message = snatch_message, data = rls) diff --git a/couchpotato/core/plugins/score/main.py b/couchpotato/core/plugins/score/main.py index e6fef253..08a18551 100644 --- a/couchpotato/core/plugins/score/main.py +++ b/couchpotato/core/plugins/score/main.py @@ -24,7 +24,7 @@ class Score(Plugin): try: preferred_words = removeDuplicate(preferred_words + splitString(movie['category']['preferred'].lower())) except: pass - score = nameScore(toUnicode(nzb['name']), movie['info']['year'], preferred_words) + score = nameScore(toUnicode(nzb['name']), movie['info'].get('year'), preferred_words) for movie_title in movie['info']['titles']: score += nameRatioScore(toUnicode(nzb['name']), toUnicode(movie_title)) diff --git a/couchpotato/core/plugins/score/scores.py b/couchpotato/core/plugins/score/scores.py index a53608c0..ddae4305 100644 --- a/couchpotato/core/plugins/score/scores.py +++ b/couchpotato/core/plugins/score/scores.py @@ -44,7 +44,7 @@ def nameScore(name, year, preferred_words): score += add # points if the year is correct - if str(year) in name: + if year and str(year) in name: score += 5 # Contains preferred word