Merge branch 'tv_searcher' into tv
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 ['<name_one>', '<name_one> <suffix_one>', '<name_two>', ...])
|
||||
suffixes = [None, root_library['info']['year']]
|
||||
suffixes = [None, root['info']['year']]
|
||||
|
||||
titles = [
|
||||
title + ((' %s' % suffix) if suffix else '')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user