New: Anime support
New: pull alternate names from thexem.de
New: Search using all alternate names (if rage ID is unavailable)
New: Show scene mapping information when hovering over episode number
New: Full season searching for anime (searches for each episode)
New: animezb.com anime indexer
New: Treat BD as bluray

Fixed: Parsing of 2 digit absolute episode numbers
Fixed: Loading series details page for series that start with period
Fixed: Return 0 results when manual search fails, instead of an error
Fixed: animezb URL
This commit is contained in:
Mark McDowall
2014-05-19 12:14:41 -07:00
parent 828dd5f5ad
commit 193672b652
105 changed files with 1901 additions and 364 deletions
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public override string ToString()
{
return string.Format("[{0} : {1:00}]", SceneTitle, AbsoluteEpisodeNumber);
return string.Format("[{0} : {1:00}]", Series.Title, AbsoluteEpisodeNumber);
}
}
}
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public override string ToString()
{
return string.Format("[{0} : {1}", SceneTitle, AirDate);
return string.Format("[{0} : {1}", Series.Title, AirDate);
}
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Core.Tv;
@@ -12,14 +13,14 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public Series Series { get; set; }
public string SceneTitle { get; set; }
public List<String> SceneTitles { get; set; }
public List<Episode> Episodes { get; set; }
public string QueryTitle
public List<String> QueryTitles
{
get
{
return GetQueryTitle(SceneTitle);
return SceneTitles.Select(GetQueryTitle).ToList();
}
}
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public override string ToString()
{
return string.Format("[{0} : S{1:00}]", SceneTitle, SeasonNumber);
return string.Format("[{0} : S{1:00}]", Series.Title, SeasonNumber);
}
}
}
@@ -7,7 +7,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public override string ToString()
{
return string.Format("[{0} : S{1:00}E{2:00}]", SceneTitle, SeasonNumber, EpisodeNumber);
return string.Format("[{0} : S{1:00}E{2:00}]", Series.Title, SeasonNumber, EpisodeNumber);
}
}
}
@@ -11,7 +11,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public override string ToString()
{
return string.Format("[{0} : {1}]", SceneTitle, String.Join(",", EpisodeQueryTitles));
return string.Format("[{0} : {1}]", Series.Title, String.Join(",", EpisodeQueryTitles));
}
}
}
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.Remoting.Messaging;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
@@ -84,6 +86,71 @@ namespace NzbDrone.Core.IndexerSearch
return SearchSingle(series, episode);
}
public List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber)
{
var series = _seriesService.GetSeries(seriesId);
var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
if (series.SeriesType == SeriesTypes.Anime)
{
return SearchAnimeSeason(series, episodes);
}
if (seasonNumber == 0)
{
// search for special episodes in season 0
return SearchSpecial(series, episodes);
}
var downloadDecisions = new List<DownloadDecision>();
if (series.UseSceneNumbering)
{
var sceneSeasonGroups = episodes.GroupBy(v =>
{
if (v.SceneSeasonNumber == 0 && v.SceneEpisodeNumber == 0)
return v.SeasonNumber;
else
return v.SceneSeasonNumber;
}).Distinct();
foreach (var sceneSeasonEpisodes in sceneSeasonGroups)
{
if (sceneSeasonEpisodes.Count() == 1)
{
var episode = sceneSeasonEpisodes.First();
var searchSpec = Get<SingleEpisodeSearchCriteria>(series, sceneSeasonEpisodes.ToList());
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
if (episode.SceneSeasonNumber == 0 && episode.SceneEpisodeNumber == 0)
searchSpec.EpisodeNumber = episode.EpisodeNumber;
else
searchSpec.EpisodeNumber = episode.SceneEpisodeNumber;
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
downloadDecisions.AddRange(decisions);
}
else
{
var searchSpec = Get<SeasonSearchCriteria>(series, sceneSeasonEpisodes.ToList());
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
downloadDecisions.AddRange(decisions);
}
}
}
else
{
var searchSpec = Get<SeasonSearchCriteria>(series, episodes);
searchSpec.SeasonNumber = seasonNumber;
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
downloadDecisions.AddRange(decisions);
}
return downloadDecisions;
}
private List<DownloadDecision> SearchSingle(Series series, Episode episode)
{
var searchSpec = Get<SingleEpisodeSearchCriteria>(series, new List<Episode>{episode});
@@ -123,10 +190,17 @@ namespace NzbDrone.Core.IndexerSearch
private List<DownloadDecision> SearchAnime(Series series, Episode episode)
{
var searchSpec = Get<AnimeEpisodeSearchCriteria>(series, new List<Episode> { episode });
// TODO: Get the scene title from TheXEM
searchSpec.SceneTitle = series.Title;
// TODO: Calculate the Absolute Episode Number on the fly (if I have to)
searchSpec.AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber.GetValueOrDefault(0);
searchSpec.AbsoluteEpisodeNumber = episode.SceneAbsoluteEpisodeNumber.GetValueOrDefault(0);
if (searchSpec.AbsoluteEpisodeNumber == 0)
{
searchSpec.AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber.GetValueOrDefault(0);
}
if (searchSpec.AbsoluteEpisodeNumber == 0)
{
throw new ArgumentOutOfRangeException("AbsoluteEpisodeNumber", "Can not search for an episode absolute episode number of zero");
}
return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
}
@@ -136,67 +210,19 @@ namespace NzbDrone.Core.IndexerSearch
var searchSpec = Get<SpecialEpisodeSearchCriteria>(series, episodes);
// build list of queries for each episode in the form: "<series> <episode-title>"
searchSpec.EpisodeQueryTitles = episodes.Where(e => !String.IsNullOrWhiteSpace(e.Title))
.Select(e => searchSpec.QueryTitle + " " + SearchCriteriaBase.GetQueryTitle(e.Title))
.SelectMany(e => searchSpec.QueryTitles.Select(title => title + " " + SearchCriteriaBase.GetQueryTitle(e.Title)))
.ToArray();
return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
}
public List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber)
private List<DownloadDecision> SearchAnimeSeason(Series series, List<Episode> episodes)
{
var series = _seriesService.GetSeries(seriesId);
var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
var downloadDecisions = new List<DownloadDecision>();
if (seasonNumber == 0)
foreach (var episode in episodes)
{
// search for special episodes in season 0
return SearchSpecial(series, episodes);
}
List<DownloadDecision> downloadDecisions = new List<DownloadDecision>();
if (series.UseSceneNumbering)
{
var sceneSeasonGroups = episodes.GroupBy(v =>
{
if (v.SceneSeasonNumber == 0 && v.SceneEpisodeNumber == 0)
return v.SeasonNumber;
else
return v.SceneSeasonNumber;
}).Distinct();
foreach (var sceneSeasonEpisodes in sceneSeasonGroups)
{
if (sceneSeasonEpisodes.Count() == 1)
{
var episode = sceneSeasonEpisodes.First();
var searchSpec = Get<SingleEpisodeSearchCriteria>(series, sceneSeasonEpisodes.ToList());
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
if (episode.SceneSeasonNumber == 0 && episode.SceneEpisodeNumber == 0)
searchSpec.EpisodeNumber = episode.EpisodeNumber;
else
searchSpec.EpisodeNumber = episode.SceneEpisodeNumber;
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
downloadDecisions.AddRange(decisions);
}
else
{
var searchSpec = Get<SeasonSearchCriteria>(series, sceneSeasonEpisodes.ToList());
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
downloadDecisions.AddRange(decisions);
}
}
}
else
{
var searchSpec = Get<SeasonSearchCriteria>(series, episodes);
searchSpec.SeasonNumber = seasonNumber;
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
downloadDecisions.AddRange(decisions);
downloadDecisions.AddRange(SearchAnime(series, episode));
}
return downloadDecisions;
@@ -207,13 +233,14 @@ namespace NzbDrone.Core.IndexerSearch
var spec = new TSpec();
spec.Series = series;
spec.SceneTitle = _sceneMapping.GetSceneName(series.TvdbId);
spec.SceneTitles = _sceneMapping.GetSceneNames(series.TvdbId,
episodes.Select(e => e.SeasonNumber)
.Concat(episodes.Select(e => e.SceneSeasonNumber)
.Distinct()));
spec.Episodes = episodes;
if (string.IsNullOrWhiteSpace(spec.SceneTitle))
{
spec.SceneTitle = series.Title;
}
spec.SceneTitles.Add(series.Title);
return spec;
}