def get_series_id(title): norm_series_name = normalize_series_name(title) series_name = urllib.quote_plus(norm_series_name) url = search_show + api_key + series_name series = None try: response = requests.get(url) except requests.RequestException: log.warning('Request failed %s' % url) return try: data = response.json() except ValueError: raise LookupError('Error Parsing Traktv Json for %s' % title) if 'error' in data: raise LookupError('Error from trakt: %s' % data['error']) for item in data: if normalize_series_name(item['title']) == norm_series_name: series = item['tvdb_id'] break else: for item in data: title_match = SequenceMatcher(lambda x: x in '\t', normalize_series_name(item['title']), norm_series_name).ratio() if title_match > .9: log.debug('Warning: Using lazy matching because title was not found exactly for %s' % title) series = item['tvdb_id'] break if not series: raise LookupError('Could not find tvdb_id for `%s` on trakt' % title) return series
def get_series_id(title): norm_series_name = normalize_series_name(title) series_name = urllib.quote_plus(norm_series_name) url = search_show + api_key + series_name series = None try: response = requests.get(url) except requests.RequestException: log.warning('Request failed %s' % url) return try: data = response.json() except ValueError: log.debug('Error Parsing Traktv Json for %s' % title) return if 'status' in data: log.debug('Returned Status %s' % data['status']) else: for item in data: if normalize_series_name(item['title']) == norm_series_name: series = item['tvdb_id'] if not series: for item in data: title_match = SequenceMatcher( lambda x: x in '\t', normalize_series_name(item['title']), norm_series_name).ratio() if not series and title_match > .9: log.debug( 'Warning: Using lazy matching because title was not found exactly for %s' % title) series = item['tvdb_id'] if not series: log.debug('Trakt.tv Returns only EXACT Name Matching: %s' % title) return series
def get_series_id(title): norm_series_name = normalize_series_name(title) series_name = urllib.quote_plus(norm_series_name) url = search_show + api_key + series_name series = None try: data = requests.get(url) except requests.RequestException: log.warning('Request failed %s' % url) return if data: try: data = data.json() except ValueError: log.debug('Error Parsing Traktv Json for %s' % title) if data: if 'status' in data: log.debug('Returned Status %s' % data['status']) else: for item in data: if normalize_series_name(item['title']) == norm_series_name: series = item['tvdb_id'] if not series: for item in data: title_match = SequenceMatcher(lambda x: x in '\t', normalize_series_name(item['title']), norm_series_name).ratio() if not series and title_match > .9: log.debug('Warning: Using lazy matching because title was not found exactly for %s' % title) series = item['tvdb_id'] if series: return series else: log.debug('Trakt.tv Returns only EXACT Name Matching: %s' % title) return series
def get_series_id(title): norm_series_name = normalize_series_name(title) series_name = urllib.quote_plus(norm_series_name) url = search_show + api_key + series_name series = None try: response = requests.get(url) except requests.RequestException: log.warning("Request failed %s" % url) return try: data = response.json() except ValueError: raise LookupError("Error Parsing Traktv Json for %s" % title) if "error" in data: raise LookupError("Error from trakt: %s" % data["error"]) for item in data: if normalize_series_name(item["title"]) == norm_series_name: series = item["tvdb_id"] break else: for item in data: title_match = SequenceMatcher( lambda x: x in "\t", normalize_series_name(item["title"]), norm_series_name ).ratio() if title_match > 0.9: log.debug("Warning: Using lazy matching because title was not found exactly for %s" % title) series = item["tvdb_id"] break if not series: raise LookupError("Could not find tvdb_id for `%s` on trakt" % title) return series
def on_task_metainfo(self, task, config): if not config: # Don't run when we are disabled return # Generate the group settings for series plugin group_settings = {} if isinstance(config, dict): group_settings = config # Generate a list of unique series that metainfo_series can parse for this task metainfo_series = get_plugin_by_name('metainfo_series') guess_entry = metainfo_series.instance.guess_entry guessed_series = {} for entry in task.entries: if guess_entry(entry): guessed_series.setdefault( normalize_series_name(entry['series_name']), entry['series_name']) # Combine settings and series into series plugin config format allseries = { 'settings': { 'all_series': group_settings }, 'all_series': guessed_series.values() } # Merge our config in to the main series config self.merge_config(task, allseries)
def post(self, session): """ Create a new show and set its first accepted episode and/or alternate names """ data = request.json series_name = data.get('name') normalized_name = series.normalize_series_name(series_name) matches = series.shows_by_exact_name(normalized_name, session=session) if matches: raise Conflict('Show `%s` already exist in DB' % series_name) show = series.Series() show.name = series_name session.add(show) ep_id = data.get('begin_episode') alt_names = data.get('alternate_names') if ep_id: series.set_series_begin(show, ep_id) if alt_names: try: series.set_alt_names(alt_names, show, session) except PluginError as e: # Alternate name already exist for a different show raise Conflict(e.value) session.commit() rsp = jsonify(series_details(show, begin=ep_id is not None)) rsp.status_code = 201 return rsp
def on_task_metainfo(self, task, config): if not config: # Don't run when we are disabled return if task.is_rerun: # Since we are running after task start phase, make sure not to merge into the config again on reruns return # Generate the group settings for series plugin group_settings = {} if isinstance(config, dict): group_settings = config group_settings['identified_by'] = 'ep' # Generate a list of unique series that metainfo_series can parse for this task metainfo_series = plugin.get_plugin_by_name('metainfo_series') guess_entry = metainfo_series.instance.guess_entry guessed_series = {} for entry in task.entries: if guess_entry(entry, config=group_settings): guessed_series.setdefault( normalize_series_name(entry['series_name']), entry['series_name']) # Combine settings and series into series plugin config format allseries = { 'settings': { 'all_series': group_settings }, 'all_series': list(guessed_series.values()) } # Merge our config in to the main series config self.merge_config(task, allseries)
def on_task_output(self, task, config): if not config: return with Session() as session: for entry in task.accepted: if 'series_name' in entry and 'series_id' in entry: series_name = entry['series_name'].replace(r'\!', '!') normalized_name = normalize_series_name(series_name) series = shows_by_exact_name(normalized_name, session) if not series: log.info('Series not yet in database, adding `%s`' % series_name) series = Series() series.name = series_name session.add(series) else: series = series[0] try: add_series_entity(session, series, entry['series_id'], quality=entry['quality']) except ValueError as e: log.warn(e.args[0]) else: log.info('Added entity `%s` to series `%s`.' % (entry['series_id'], series.name.title()))
def on_task_input(self, task, config): """asd""" slist = task.session.query(Series) if isinstance(config, basestring): name = normalize_series_name(config) slist = slist.filter(Series._name_normalized.contains(name)) slist = ( slist.outerjoin(Series.episodes).outerjoin( Episode.releases).outerjoin(Series.in_tasks). # group_by(Series.id).having(func.count(SeriesTask.id) < 1).order_by(Series.name).all()) # group_by(Series.id).having(func.count(SeriesTask.id) >= 1).order_by(Series.name).all()) order_by(Series.name).all()) entries = [] for series in slist: elist = (task.session.query(Episode).filter( Episode.series_id == series.id).order_by( Episode.season, Episode.number).all()) for episode in elist: self.log.debug('Found episode %s for series "%s"' % (episode.identifier, series.name)) entry = Entry() entry['title'] = '%s %s' % (series.name, episode.identifier) entry['url'] = 'http://localhost/mock/%s' % hash( entry['title']) if entry.isvalid(): entries.append(entry) else: self.log.debug('Invalid entry created? %s' % entry) return entries
def post(self, session): """ Create a new show and set its first accepted episode and/or alternate names """ data = request.json series_name = data.get('series_name') normalized_name = series.normalize_series_name(series_name) matches = series.shows_by_exact_name(normalized_name, session=session) if matches: return {'status': 'error', 'message': 'Show `%s` already exist in DB' % series_name }, 500 show = series.Series() show.name = series_name session.add(show) ep_id = data.get('episode_identifier') alt_names = data.get('alternate_names') if ep_id: try: series.set_series_begin(show, ep_id) except ValueError as e: return {'status': 'error', 'message': e.args[0] }, 501 if alt_names: try: series.set_alt_names(alt_names, show, session) except PluginError as e: return {'status': 'error', 'message': e.value }, 502 return jsonify(get_series_details(show))
def add(manager, options): series_name = options.series_name entity_ids = options.entity_id quality = options.quality or os.environ.get(ENV_ADD_QUALITY, None) series_name = series_name.replace(r'\!', '!') normalized_name = normalize_series_name(series_name) with Session() as session: series = shows_by_exact_name(normalized_name, session) if not series: console('Series not yet in database, adding `%s`' % series_name) series = Series() series.name = series_name session.add(series) else: series = series[0] for ent_id in entity_ids: try: add_series_entity(session, series, ent_id, quality=quality) except ValueError as e: console(e.args[0]) else: console('Added entity `%s` to series `%s`.' % (ent_id, series.name.title())) session.commit() manager.config_changed()
def begin(manager, options): series_name = options.series_name series_name = series_name.replace(r'\!', '!') normalized_name = normalize_series_name(series_name) with Session() as session: series = shows_by_exact_name(normalized_name, session) if options.forget: if not series: console('Series `%s` was not found in the database.' % series_name) else: series = series[0] series.begin = None console('The begin episode for `%s` has been forgotten.' % series.name) session.commit() manager.config_changed() elif options.episode_id: ep_id = options.episode_id if not series: console('Series not yet in database. Adding `%s`.' % series_name) series = Series() series.name = series_name session.add(series) else: series = series[0] try: _, entity_type = set_series_begin(series, ep_id) except ValueError as e: console(e) else: if entity_type == 'season': console('`%s` was identified as a season.' % ep_id) ep_id += 'E01' console('Releases for `%s` will be accepted starting with `%s`.' % (series.name, ep_id)) session.commit() manager.config_changed()
def on_task_metainfo(self, task, config): if not config: # Don't run when we are disabled return # Generate the group settings for series plugin group_settings = {} allow_seasonless = False if isinstance(config, dict): allow_seasonless = config.pop('allow_seasonless', False) group_settings = config # Generate a list of unique series that have premieres metainfo_series = get_plugin_by_name('metainfo_series') guess_entry = metainfo_series.instance.guess_entry # Make a set of unique series according to series name normalization rules guessed_series = {} for entry in task.entries: if guess_entry(entry, allow_seasonless=allow_seasonless): if entry['series_season'] == 1 and entry['series_episode'] in (0, 1): guessed_series.setdefault(normalize_series_name(entry['series_name']), entry['series_name']) # Reject any further episodes in those series for entry in task.entries: for series in guessed_series.itervalues(): if entry.get('series_name') == series and not ( entry.get('series_season') == 1 and entry.get('series_episode') in (0, 1) ): task.reject(entry, 'Non premiere episode in a premiere series') # Combine settings and series into series plugin config format allseries = {'settings': {'series_premiere': group_settings}, 'series_premiere': guessed_series.values()} # Merge the our config in to the main series config self.merge_config(task, allseries)
def on_task_metainfo(self, task, config): if not config: # Don't run when we are disabled return if task.is_rerun: return # Generate the group settings for series plugin group_settings = {} allow_seasonless = False desired_eps = [0, 1] if isinstance(config, dict): allow_seasonless = config.pop('allow_seasonless', False) if not config.pop('allow_teasers', True): desired_eps = [1] group_settings = config group_settings['identified_by'] = 'ep' # Generate a list of unique series that have premieres metainfo_series = plugin.get_plugin_by_name('metainfo_series') guess_entry = metainfo_series.instance.guess_entry # Make a set of unique series according to series name normalization rules guessed_series = {} for entry in task.entries: if guess_entry(entry, allow_seasonless=allow_seasonless, config=group_settings): if not entry['season_pack'] and entry[ 'series_season'] == 1 and entry[ 'series_episode'] in desired_eps: normalized_name = normalize_series_name( entry['series_name']) db_series = task.session.query(Series).filter( Series.name == normalized_name).first() if db_series and db_series.in_tasks: continue guessed_series.setdefault(normalized_name, entry['series_name']) # Reject any further episodes in those series for entry in task.entries: for series in guessed_series.values(): if entry.get('series_name') == series and \ (entry.get('season_pack') or not ( entry.get('series_season') == 1 and entry.get('series_episode') in desired_eps)): entry.reject( 'Non premiere episode or season pack in a premiere series' ) # Since we are running after task start phase, make sure not to merge into the config multiple times on reruns if not task.is_rerun: # Combine settings and series into series plugin config format allseries = { 'settings': { 'series_premiere': group_settings }, 'series_premiere': list(guessed_series.values()) } # Merge the our config in to the main series config self.merge_config(task, allseries)
def display_details(name): """Display detailed series information, ie. series show NAME""" with Session() as session: name = normalize_series_name(name) # Sort by length of name, so that partial matches always show shortest matching title matches = shows_by_name(name, session=session) if not matches: console('ERROR: Unknown series `%s`' % name) return # Pick the best matching series series = matches[0] console('Showing results for `%s`.' % series.name) if len(matches) > 1: console('WARNING: Multiple series match to `%s`.' % name) console('Be more specific to see the results of other matches:') for s in matches[1:]: console(' - %s' % s.name) console(' %-63s%-15s' % ('Identifier, Title', 'Quality')) console('-' * 79) episodes = show_episodes(series, session=session) for episode in episodes: if episode.identifier is None: console(' None <--- Broken!') else: console(' %s (%s) - %s' % (episode.identifier, episode.identified_by or 'N/A', episode.age)) for release in episode.releases: status = release.quality.name title = release.title if len(title) > 55: title = title[:55] + '...' if release.proper_count > 0: status += '-proper' if release.proper_count > 1: status += str(release.proper_count) if release.downloaded: console(' * %-60s%-15s' % (title, status)) else: console(' %-60s%-15s' % (title, status)) console('-' * 79) console(' * = downloaded') if not series.identified_by: console('') console(' Series plugin is still learning which episode numbering mode is ') console(' correct for this series (identified_by: auto).') console(' Few duplicate downloads can happen with different numbering schemes') console(' during this time.') else: console(' Series uses `%s` mode to identify episode numbering (identified_by).' % series.identified_by) console(' See option `identified_by` for more information.') if series.begin: console(' Begin episode for this series set to `%s`.' % series.begin.identifier)
def get(self, name, session): """ List of shows matching lookup name """ name = series.normalize_series_name(name) matches = series.shows_by_name(name, session=session) shows = [] for match in matches: shows.append(get_series_details(match)) return jsonify({'shows': shows, 'number_of_shows': len(shows)})
def get(self, name, session): """ List of shows matching lookup name """ name = series.normalize_series_name(name) matches = series.shows_by_name(name, session=session) shows = [] for match in matches: shows.append(get_series_details(match)) return jsonify({ 'shows': shows, 'number_of_shows': len(shows) })
def get(self, name, session): """ List of shows matching lookup name """ name = series.normalize_series_name(name) matches = series.shows_by_name(name, session=session) args = series_list_parser.parse_args() begin = args.get('begin') latest = args.get('latest') shows = [] for match in matches: shows.append(series_details(match, begin, latest)) return jsonify(shows)
def lookup_episode(title=None, seasonnum=None, episodenum=None, tvdb_id=None, session=None, only_cached=False): series = ApiTrakt.lookup_series(title=title, tvdb_id=tvdb_id, only_cached=only_cached, session=session) if not series: raise LookupError('Could not identify series') if series.tvdb_id: ep_description = '%s.S%sE%s' % (series.title, seasonnum, episodenum) episode = session.query(TraktEpisode).filter(TraktEpisode.series_id == series.tvdb_id).\ filter(TraktEpisode.season == seasonnum).filter(TraktEpisode.number == episodenum).first() url = episode_summary + api_key + '%s/%s/%s' % (series.tvdb_id, seasonnum, episodenum) elif title: title = normalize_series_name(title) title = re.sub(' ', '-', title) ep_description = '%s.S%sE%s' % (series.title, seasonnum, episodenum) episode = session.query(TraktEpisode).filter(title == series.title).\ filter(TraktEpisode.season == seasonnum).\ filter(TraktEpisode.number == episodenum).first() url = episode_summary + api_key + '%s/%s/%s' % (title, seasonnum, episodenum) if not episode: if only_cached: raise LookupError('Episode %s not found in cache' % ep_description) log.debug('Episode %s not found in cache, looking up from trakt.' % ep_description) try: data = requests.get(url) except requests.RequestException: log.debug('Error Retrieving Trakt url: %s' % url) try: data = data.json() except ValueError: log.debug('Error parsing Trakt episode json for %s' % title) if data: if 'status' in data: raise LookupError('Error looking up episode') ep_data = data['episode'] if ep_data: episode = session.query(TraktEpisode).filter(TraktEpisode.tvdb_id == ep_data['tvdb_id']).first() if not episode: ep_data['episode_name'] = ep_data.pop('title') for i in ep_data['images']: ep_data[i] = ep_data['images'][i] del ep_data['images'] episode = TraktEpisode(ep_data) series.episodes.append(episode) session.merge(episode) if episode: return episode else: raise LookupError('No results found for (%s)' % episode)
def post(self, name, session): """ Create a new show and set its first accepted episode """ normalized_name = series.normalize_series_name(name) matches = series.shows_by_exact_name(normalized_name, session=session) if matches: return { 'status': 'error', 'message': 'Show `%s` already exist in DB' % name }, 500 show = series.Series() show.name = name session.add(show) data = request.json ep_id = data.get('episode_identifier') try: series.set_series_begin(show, ep_id) except ValueError as e: return {'status': 'error', 'message': e.args[0]}, 400 return jsonify(get_series_details(show))
def on_task_metainfo(self, task, config): if not config: # Don't run when we are disabled return # Generate the group settings for series plugin group_settings = {} if isinstance(config, dict): group_settings = config # Generate a list of unique series that metainfo_series can parse for this task metainfo_series = get_plugin_by_name('metainfo_series') guess_entry = metainfo_series.instance.guess_entry guessed_series = {} for entry in task.entries: if guess_entry(entry): guessed_series.setdefault(normalize_series_name(entry['series_name']), entry['series_name']) # Combine settings and series into series plugin config format allseries = {'settings': {'all_series': group_settings}, 'all_series': guessed_series.values()} # Merge our config in to the main series config self.merge_config(task, allseries)
def on_task_metainfo(self, task, config): if not config: # Don't run when we are disabled return if task.is_rerun: return # Generate the group settings for series plugin group_settings = {} allow_seasonless = False desired_eps = [0, 1] if isinstance(config, dict): allow_seasonless = config.pop('allow_seasonless', False) if not config.pop('allow_teasers', True): desired_eps = [1] group_settings = config group_settings['identified_by'] = 'ep' # Generate a list of unique series that have premieres metainfo_series = plugin.get_plugin_by_name('metainfo_series') guess_entry = metainfo_series.instance.guess_entry # Make a set of unique series according to series name normalization rules guessed_series = {} for entry in task.entries: if guess_entry(entry, allow_seasonless=allow_seasonless, config=group_settings): if entry['series_season'] == 1 and entry['series_episode'] in desired_eps: normalized_name = normalize_series_name(entry['series_name']) db_series = task.session.query(Series).filter(Series.name == normalized_name).first() if db_series and db_series.in_tasks: continue guessed_series.setdefault(normalized_name, entry['series_name']) # Reject any further episodes in those series for entry in task.entries: for series in guessed_series.values(): if entry.get('series_name') == series and not ( entry.get('series_season') == 1 and entry.get('series_episode') in desired_eps): entry.reject('Non premiere episode in a premiere series') # Since we are running after task start phase, make sure not to merge into the config multiple times on reruns if not task.is_rerun: # Combine settings and series into series plugin config format allseries = {'settings': {'series_premiere': group_settings}, 'series_premiere': list(guessed_series.values())} # Merge the our config in to the main series config self.merge_config(task, allseries)
def post(self, name, session): """ Create a new show and set its first accepted episode """ normalized_name = series.normalize_series_name(name) matches = series.shows_by_exact_name(normalized_name, session=session) if matches: return {'status': 'error', 'message': 'Show `%s` already exist in DB' % name }, 500 show = series.Series() show.name = name session.add(show) data = request.json ep_id = data.get('episode_identifier') try: series.set_series_begin(show, ep_id) except ValueError as e: return {'status': 'error', 'message': e.args[0] }, 400 return jsonify(get_series_details(show))
def begin(manager, options): series_name = options.series_name ep_id = options.episode_id normalized_name = normalize_series_name(series_name) with Session() as session: series = shows_by_exact_name(normalized_name, session) if not series: console('Series not yet in database, adding `%s`' % series_name) series = Series() series.name = series_name session.add(series) else: series = series[0] try: set_series_begin(series, ep_id) except ValueError as e: console(e) else: console('Episodes for `%s` will be accepted starting with `%s`' % (series.name, ep_id)) session.commit() manager.config_changed()
def begin(manager, options): series_name = options.series_name series_name = series_name.replace(r'\!', '!') normalized_name = normalize_series_name(series_name) with Session() as session: series = shows_by_exact_name(normalized_name, session) if options.forget: if not series: console('Series `%s` was not found in the database.' % series_name) else: series = series[0] series.begin = None console('The begin episode for `%s` has been forgotten.' % series.name) session.commit() manager.config_changed() elif options.episode_id: ep_id = options.episode_id if not series: console('Series not yet in database. Adding `%s`.' % series_name) series = Series() series.name = series_name session.add(series) else: series = series[0] try: _, entity_type = set_series_begin(series, ep_id) except ValueError as e: console(e) else: if entity_type == 'season': console('`%s` was identified as a season.' % ep_id) ep_id += 'E01' console( 'Releases for `%s` will be accepted starting with `%s`.' % (series.name, ep_id)) session.commit() manager.config_changed()
def on_task_metainfo(self, task, config): if not config: # Don't run when we are disabled return # Generate the group settings for series plugin group_settings = {} allow_seasonless = False if isinstance(config, dict): allow_seasonless = config.pop('allow_seasonless', False) group_settings = config # Generate a list of unique series that have premieres metainfo_series = get_plugin_by_name('metainfo_series') guess_entry = metainfo_series.instance.guess_entry # Make a set of unique series according to series name normalization rules guessed_series = {} for entry in task.entries: if guess_entry(entry, allow_seasonless=allow_seasonless): if entry['series_season'] == 1 and entry['series_episode'] in ( 0, 1): guessed_series.setdefault( normalize_series_name(entry['series_name']), entry['series_name']) # Reject any further episodes in those series for entry in task.entries: for series in guessed_series.itervalues(): if entry.get('series_name') == series and not ( entry.get('series_season') == 1 and entry.get('series_episode') in (0, 1)): task.reject(entry, 'Non premiere episode in a premiere series') # Combine settings and series into series plugin config format allseries = { 'settings': { 'series_premiere': group_settings }, 'series_premiere': guessed_series.values() } # Merge the our config in to the main series config self.merge_config(task, allseries)
def on_task_metainfo(self, task, config): if not config: # Don't run when we are disabled return if task.is_rerun: # Since we are running after task start phase, make sure not to merge into the config again on reruns return # Generate the group settings for series plugin group_settings = {} if isinstance(config, dict): group_settings = config group_settings['identified_by'] = 'ep' # Generate a list of unique series that metainfo_series can parse for this task metainfo_series = plugin.get_plugin_by_name('metainfo_series') guess_entry = metainfo_series.instance.guess_entry guessed_series = {} for entry in task.entries: if guess_entry(entry): guessed_series.setdefault(normalize_series_name(entry['series_name']), entry['series_name']) # Combine settings and series into series plugin config format allseries = {'settings': {'all_series': group_settings}, 'all_series': guessed_series.values()} # Merge our config in to the main series config self.merge_config(task, allseries)
def on_task_input(self, task, config): """asd""" slist = task.session.query(Series) if isinstance(config, basestring): name = normalize_series_name(config) slist = slist.filter(Series._name_normalized.contains(name)) slist = (slist.outerjoin(Series.episodes).outerjoin(Episode.releases).outerjoin(Series.in_tasks). # group_by(Series.id).having(func.count(SeriesTask.id) < 1).order_by(Series.name).all()) # group_by(Series.id).having(func.count(SeriesTask.id) >= 1).order_by(Series.name).all()) order_by(Series.name).all()) entries = [] for series in slist: elist = (task.session.query(Episode).filter(Episode.series_id == series.id). order_by(Episode.season, Episode.number).all()) for episode in elist: self.log.debug('Found episode %s for series "%s"' % (episode.identifier, series.name)) entry = Entry() entry['title'] = '%s %s' % (series.name, episode.identifier) entry['url'] = 'http://localhost/mock/%s' % hash(entry['title']) if entry.isvalid(): entries.append(entry) else: self.log.debug('Invalid entry created? %s' % entry) return entries
def lookup_episode(title=None, seasonnum=None, episodenum=None, tvdb_id=None, session=None, only_cached=False): series = ApiTrakt.lookup_series(title=title, tvdb_id=tvdb_id, only_cached=only_cached, session=session) if not series: raise LookupError('Could not identify series') if series.tvdb_id: ep_description = '%s.S%sE%s' % (series.title, seasonnum, episodenum) episode = session.query(TraktEpisode).filter(TraktEpisode.series_id == series.tvdb_id).\ filter(TraktEpisode.season == seasonnum).filter(TraktEpisode.number == episodenum).first() url = episode_summary + api_key + '%s/%s/%s' % ( series.tvdb_id, seasonnum, episodenum) elif title: title = normalize_series_name(title) title = re.sub(' ', '-', title) ep_description = '%s.S%sE%s' % (series.title, seasonnum, episodenum) episode = session.query(TraktEpisode).filter(title == series.title).\ filter(TraktEpisode.season == seasonnum).\ filter(TraktEpisode.number == episodenum).first() url = episode_summary + api_key + '%s/%s/%s' % (title, seasonnum, episodenum) if not episode: if only_cached: raise LookupError('Episode %s not found in cache' % ep_description) log.debug('Episode %s not found in cache, looking up from trakt.' % ep_description) try: data = requests.get(url) except requests.RequestException: log.debug('Error Retrieving Trakt url: %s' % url) try: data = data.json() except ValueError: log.debug('Error parsing Trakt episode json for %s' % title) if data: if 'status' in data: raise LookupError('Error looking up episode') ep_data = data['episode'] if ep_data: episode = session.query(TraktEpisode).filter( TraktEpisode.tvdb_id == ep_data['tvdb_id']).first() if not episode: ep_data['episode_name'] = ep_data.pop('title') for i in ep_data['images']: ep_data[i] = ep_data['images'][i] del ep_data['images'] episode = TraktEpisode(ep_data) series.episodes.append(episode) session.merge(episode) if episode: return episode else: raise LookupError('No results found for (%s)' % episode)
def get(self, session=None): """ List existing shows """ args = series_list_parser.parse_args() # Filter params configured = args['in_config'] premieres = args['premieres'] # Pagination and sorting params page = args['page'] per_page = args['per_page'] sort_by = args['sort_by'] sort_order = args['order'] name = series.normalize_series_name(args['query']) if args['query'] else None # Handle max size limit if per_page > 100: per_page = 100 descending = sort_order == 'desc' # Data params lookup = args.get('lookup') begin = args.get('begin') latest = args.get('latest') start = per_page * (page - 1) stop = start + per_page kwargs = { 'configured': configured, 'premieres': premieres, 'start': start, 'stop': stop, 'sort_by': sort_by, 'descending': descending, 'session': session, 'name': name } total_items = series.get_series_summary(count=True, **kwargs) if not total_items: return jsonify([]) series_list = [] for s in series.get_series_summary(**kwargs): series_object = series_details(s, begin, latest) series_list.append(series_object) # Total number of pages total_pages = int(ceil(total_items / float(per_page))) if total_pages < page and total_pages != 0: raise NotFoundError('page %s does not exist' % page) # Actual results in page actual_size = min(per_page, len(series_list)) # Do relevant lookups if lookup: api_client = APIClient() for endpoint in lookup: base_url = '/%s/series/' % endpoint for show in series_list: pos = series_list.index(show) series_list[pos].setdefault('lookup', {}) url = base_url + show['name'] + '/' result = api_client.get_endpoint(url) series_list[pos]['lookup'].update({endpoint: result}) # Get pagination headers pagination = pagination_headers(total_pages, total_items, actual_size, request) # Created response rsp = jsonify(series_list) # Add link header to response rsp.headers.extend(pagination) return rsp
def display_details(options): """Display detailed series information, ie. series show NAME""" name = options.series_name sort_by = options.sort_by or os.environ.get(ENV_SHOW_SORTBY_FIELD, 'age') if options.order is not None: reverse = True if options.order == 'desc' else False else: reverse = True if os.environ.get(ENV_SHOW_SORTBY_ORDER) == 'desc' else False with Session() as session: name = normalize_series_name(name) # Sort by length of name, so that partial matches always show shortest matching title matches = shows_by_name(name, session=session) if not matches: console(colorize(ERROR_COLOR, 'ERROR: Unknown series `%s`' % name)) return # Pick the best matching series series = matches[0] table_title = colorize('white', series.name) if len(matches) > 1: warning = (colorize('red', ' WARNING: ') + 'Multiple series match to `{}`.\n ' 'Be more specific to see the results of other matches:\n\n' ' {}'.format(name, ', '.join(s.name for s in matches[1:]))) if not options.table_type == 'porcelain': console(warning) header = ['Identifier', 'Last seen', 'Release titles', 'Release Quality', 'Proper'] table_data = [header] entities = get_all_entities(series, session=session, sort_by=sort_by, reverse=reverse) for entity in entities: if not entity.releases: continue if entity.identifier is None: identifier = colorize(ERROR_COLOR, 'MISSING') age = '' else: identifier = entity.identifier age = entity.age entity_data = [identifier, age] release_titles = [] release_qualities = [] release_propers = [] for release in entity.releases: title = release.title quality = release.quality.name if not release.downloaded: title = colorize(UNDOWNLOADED_RELEASE_COLOR, title) quality = quality else: title += ' *' title = colorize(DOWNLOADED_RELEASE_COLOR, title) quality = quality release_titles.append(title) release_qualities.append(quality) release_propers.append('Yes' if release.proper_count > 0 else '') entity_data.append('\n'.join(release_titles)) entity_data.append('\n'.join(release_qualities)) entity_data.append('\n'.join(release_propers)) table_data.append(entity_data) footer = ' %s \n' % (colorize(DOWNLOADED_RELEASE_COLOR, '* Downloaded')) if not series.identified_by: footer += ('\n Series plugin is still learning which episode numbering mode is \n' ' correct for this series (identified_by: auto).\n' ' Few duplicate downloads can happen with different numbering schemes\n' ' during this time.') else: footer += '\n `%s` uses `%s` mode to identify episode numbering.' % (series.name, series.identified_by) begin_text = 'option' if series.begin: footer += ' \n Begin for `%s` is set to `%s`.' % (series.name, series.begin.identifier) begin_text = 'and `begin` options' footer += ' \n See `identified_by` %s for more information.' % begin_text try: table = TerminalTable(options.table_type, table_data, table_title, drop_columns=[4, 3, 1]) console(table.output) except TerminalTableError as e: console('ERROR: %s' % str(e)) return if not options.table_type == 'porcelain': console(footer)
def display_details(name): """Display detailed series information, ie. series show NAME""" from flexget.manager import Session with contextlib.closing(Session()) as session: name = normalize_series_name(name) # Sort by length of name, so that partial matches always show shortest matching title matches = (session.query(Series).filter(Series._name_normalized.contains(name)). order_by(func.char_length(Series.name)).all()) if not matches: console('ERROR: Unknown series `%s`' % name) return # Pick the best matching series series = matches[0] console('Showing results for `%s`.' % series.name) if len(matches) > 1: console('WARNING: Multiple series match to `%s`.' % name) console('Be more specific to see the results of other matches:') for s in matches[1:]: console(' - %s' % s.name) console(' %-63s%-15s' % ('Identifier, Title', 'Quality')) console('-' * 79) # Query episodes in sane order instead of iterating from series.episodes episodes = session.query(Episode).filter(Episode.series_id == series.id) if series.identified_by == 'sequence': episodes = episodes.order_by(Episode.number).all() elif series.identified_by == 'ep': episodes = episodes.order_by(Episode.season, Episode.number).all() else: episodes = episodes.order_by(Episode.identifier).all() for episode in episodes: if episode.identifier is None: console(' None <--- Broken!') else: console(' %s (%s) - %s' % (episode.identifier, episode.identified_by or 'N/A', episode.age)) for release in episode.releases: status = release.quality.name title = release.title if len(title) > 55: title = title[:55] + '...' if release.proper_count > 0: status += '-proper' if release.proper_count > 1: status += str(release.proper_count) if release.downloaded: console(' * %-60s%-15s' % (title, status)) else: console(' %-60s%-15s' % (title, status)) console('-' * 79) console(' * = downloaded') if not series.identified_by: console('') console(' Series plugin is still learning which episode numbering mode is ') console(' correct for this series (identified_by: auto).') console(' Few duplicate downloads can happen with different numbering schemes') console(' during this time.') else: console(' Series uses `%s` mode to identify episode numbering (identified_by).' % series.identified_by) console(' See option `identified_by` for more information.') if series.begin: console(' Begin episode for this series set to `%s`.' % series.begin.identifier)
def display_details(options): """Display detailed series information, ie. series show NAME""" name = options.series_name with Session() as session: name = normalize_series_name(name) # Sort by length of name, so that partial matches always show shortest matching title matches = shows_by_name(name, session=session) if not matches: console(colorize(ERROR_COLOR, 'ERROR: Unknown series `%s`' % name)) return # Pick the best matching series series = matches[0] table_title = colorize('white', series.name) if len(matches) > 1: warning = (colorize('red', ' WARNING: ') + 'Multiple series match to `{}`.\n ' 'Be more specific to see the results of other matches:\n\n' ' {}'.format(name, ', '.join(s.name for s in matches[1:]))) if not options.table_type == 'porcelain': console(warning) header = ['Episode ID', 'Latest age', 'Release titles', 'Release Quality', 'Proper'] table_data = [header] episodes = show_episodes(series, session=session) for episode in episodes: if episode.identifier is None: identifier = colorize(ERROR_COLOR, 'MISSING') age = '' else: identifier = episode.identifier age = episode.age ep_data = [identifier, age] release_titles = [] release_qualities = [] release_propers = [] for release in episode.releases: title = release.title quality = release.quality.name if not release.downloaded: title = colorize(UNDOWNLOADED_RELEASE_COLOR, title) quality = quality else: title = colorize(DOWNLOADED_RELEASE_COLOR, title) quality = quality release_titles.append(title) release_qualities.append(quality) release_propers.append('Yes' if release.proper_count > 0 else '') ep_data.append('\n'.join(release_titles)) ep_data.append('\n'.join(release_qualities)) ep_data.append('\n'.join(release_propers)) table_data.append(ep_data) footer = (' %s %s\n' % (colorize(DOWNLOADED_RELEASE_COLOR, 'Downloaded'), colorize(UNDOWNLOADED_RELEASE_COLOR, 'Un-downloaded'))) if not series.identified_by: footer += ('\n' ' Series plugin is still learning which episode numbering mode is \n' ' correct for this series (identified_by: auto).\n' ' Few duplicate downloads can happen with different numbering schemes\n' ' during this time.') else: footer += ' \n Series uses `%s` mode to identify episode numbering (identified_by).' % series.identified_by footer += ' \n See option `identified_by` for more information.\n' if series.begin: footer += ' Begin episode for this series set to `%s`.' % series.begin.identifier table = TerminalTable(options.table_type, table_data, table_title, drop_columns=[4, 3, 1]) try: console(table.output) except TerminalTableError as e: console('ERROR: %s' % str(e)) return if not options.table_type == 'porcelain': console(footer)
def display_details(options): """Display detailed series information, ie. series show NAME""" name = options.series_name with Session() as session: name = normalize_series_name(name) # Sort by length of name, so that partial matches always show shortest matching title matches = shows_by_name(name, session=session) if not matches: console(colorize(ERROR_COLOR, 'ERROR: Unknown series `%s`' % name)) return # Pick the best matching series series = matches[0] table_title = colorize('white', series.name) if len(matches) > 1: warning = ( colorize('red', ' WARNING: ') + 'Multiple series match to `{}`.\n ' 'Be more specific to see the results of other matches:\n\n' ' {}'.format(name, ', '.join(s.name for s in matches[1:]))) if not options.table_type == 'porcelain': console(warning) header = [ 'Entity ID', 'Latest age', 'Release titles', 'Release Quality', 'Proper' ] table_data = [header] entities = get_all_entities(series, session=session) for entity in entities: if entity.identifier is None: identifier = colorize(ERROR_COLOR, 'MISSING') age = '' else: identifier = entity.identifier age = entity.age entity_data = [identifier, age] release_titles = [] release_qualities = [] release_propers = [] for release in entity.releases: title = release.title quality = release.quality.name if not release.downloaded: title = colorize(UNDOWNLOADED_RELEASE_COLOR, title) quality = quality else: title += ' *' title = colorize(DOWNLOADED_RELEASE_COLOR, title) quality = quality release_titles.append(title) release_qualities.append(quality) release_propers.append( 'Yes' if release.proper_count > 0 else '') entity_data.append('\n'.join(release_titles)) entity_data.append('\n'.join(release_qualities)) entity_data.append('\n'.join(release_propers)) table_data.append(entity_data) footer = ' %s \n' % (colorize(DOWNLOADED_RELEASE_COLOR, '* Downloaded')) if not series.identified_by: footer += ( '\n Series plugin is still learning which episode numbering mode is \n' ' correct for this series (identified_by: auto).\n' ' Few duplicate downloads can happen with different numbering schemes\n' ' during this time.') else: footer += '\n Series uses `%s` mode to identify episode numbering (identified_by).' % series.identified_by footer += ' \n See option `identified_by` for more information.\n' if series.begin: footer += ' Begin episode for this series set to `%s`.' % series.begin.identifier try: table = TerminalTable(options.table_type, table_data, table_title, drop_columns=[4, 3, 1]) console(table.output) except TerminalTableError as e: console('ERROR: %s' % str(e)) return if not options.table_type == 'porcelain': console(footer)
def display_details(name): """Display detailed series information, ie. series show NAME""" from flexget.manager import Session session = Session() name = normalize_series_name(name) # Sort by length of name, so that partial matches always show shortest matching title matches = (session.query(Series).filter( Series._name_normalized.contains(name)).order_by( func.char_length(Series.name)).all()) if not matches: console('ERROR: Unknown series `%s`' % name) return # Pick the best matching series series = matches[0] console('Showing results for `%s`.' % series.name) if len(matches) > 1: console('WARNING: Multiple series match to `%s`.' % name) console('Be more specific to see the results of other matches:') for s in matches[1:]: console(' - %s' % s.name) console(' %-63s%-15s' % ('Identifier, Title', 'Quality')) console('-' * 79) # Query episodes in sane order instead of iterating from series.episodes episodes = session.query(Episode).filter(Episode.series_id == series.id) if series.identified_by == 'sequence': episodes = episodes.order_by(Episode.number).all() elif series.identified_by == 'ep': episodes = episodes.order_by(Episode.season, Episode.number).all() else: episodes = episodes.order_by(Episode.identifier).all() for episode in episodes: if episode.identifier is None: console(' None <--- Broken!') else: console(' %s (%s) - %s' % (episode.identifier, episode.identified_by or 'N/A', episode.age)) for release in episode.releases: status = release.quality.name title = release.title if len(title) > 55: title = title[:55] + '...' if release.proper_count > 0: status += '-proper' if release.proper_count > 1: status += str(release.proper_count) if release.downloaded: console(' * %-60s%-15s' % (title, status)) else: console(' %-60s%-15s' % (title, status)) console('-' * 79) console(' * = downloaded') if not series.identified_by: console('') console( ' Series plugin is still learning which episode numbering mode is ' ) console(' correct for this series (identified_by: auto).') console( ' Few duplicate downloads can happen with different numbering schemes' ) console(' during this time.') else: console( ' Series uses `%s` mode to identify episode numbering (identified_by).' % series.identified_by) console(' See option `identified_by` for more information.') if series.begin: console(' Begin episode for this series set to `%s`.' % series.begin.identifier) session.close()