1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2024-10-30 07:22:35 +01:00

New: Skyhook! fixing your calendars!

This commit is contained in:
Keivan Beigi 2015-01-08 17:43:51 -08:00
parent 3055b90178
commit 53b22035be
19 changed files with 440 additions and 20 deletions

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource.SkyHook;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories;
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{
[TestFixture]
[IntegrationTest]
public class SkyHookProxyFixture : CoreTest<SkyHookProxy>
{
[SetUp]
public void Setup()
{
UseRealHttp();
}
[TestCase(75978, "Family Guy")]
[TestCase(83462, "Castle (2009)")]
[TestCase(266189, "The Blacklist")]
public void should_be_able_to_get_series_detail(int tvdbId, string title)
{
var details = Subject.GetSeriesInfo(tvdbId);
ValidateSeries(details.Item1);
ValidateEpisodes(details.Item2);
details.Item1.Title.Should().Be(title);
}
[Test]
public void getting_details_of_invalid_series()
{
Assert.Throws<Common.Http.HttpException>(() => Subject.GetSeriesInfo(Int32.MaxValue));
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_not_have_period_at_start_of_title_slug()
{
var details = Subject.GetSeriesInfo(79099);
details.Item1.TitleSlug.Should().Be("dothack");
}
private void ValidateSeries(Series series)
{
series.Should().NotBeNull();
series.Title.Should().NotBeNullOrWhiteSpace();
series.CleanTitle.Should().Be(Parser.Parser.CleanSeriesTitle(series.Title));
series.SortTitle.Should().Be(SeriesTitleNormalizer.Normalize(series.Title, series.TvdbId));
series.Overview.Should().NotBeNullOrWhiteSpace();
series.AirTime.Should().NotBeNullOrWhiteSpace();
series.FirstAired.Should().HaveValue();
series.FirstAired.Value.Kind.Should().Be(DateTimeKind.Utc);
series.Images.Should().NotBeEmpty();
series.ImdbId.Should().NotBeNullOrWhiteSpace();
series.Network.Should().NotBeNullOrWhiteSpace();
series.Runtime.Should().BeGreaterThan(0);
series.TitleSlug.Should().NotBeNullOrWhiteSpace();
//series.TvRageId.Should().BeGreaterThan(0);
series.TvdbId.Should().BeGreaterThan(0);
}
private void ValidateEpisodes(List<Episode> episodes)
{
episodes.Should().NotBeEmpty();
var episodeGroup = episodes.GroupBy(e => e.SeasonNumber.ToString("000") + e.EpisodeNumber.ToString("000"));
episodeGroup.Should().OnlyContain(c => c.Count() == 1);
episodes.Should().Contain(c => c.SeasonNumber > 0);
episodes.Should().Contain(c => !String.IsNullOrWhiteSpace(c.Overview));
foreach (var episode in episodes)
{
ValidateEpisode(episode);
//if atleast one episdoe has title it means parse it working.
episodes.Should().Contain(c => !String.IsNullOrWhiteSpace(c.Title));
}
}
private void ValidateEpisode(Episode episode)
{
episode.Should().NotBeNull();
//TODO: Is there a better way to validate that episode number or season number is greater than zero?
(episode.EpisodeNumber + episode.SeasonNumber).Should().NotBe(0);
episode.Should().NotBeNull();
if (episode.AirDateUtc.HasValue)
{
episode.AirDateUtc.Value.Kind.Should().Be(DateTimeKind.Utc);
}
episode.Images.Any(i => i.CoverType == MediaCoverTypes.Screenshot && i.Url.Contains("-940."))
.Should()
.BeFalse();
}
}
}

View File

@ -230,6 +230,7 @@
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
<Compile Include="MetadataSourceTests\TvdbDataProxyFixture.cs" />
<Compile Include="MetadataSourceTests\SearchSeriesComparerFixture.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />

View File

@ -95,7 +95,7 @@ namespace NzbDrone.Core.Metadata.Consumers.MediaBrowser
tvShow.Add(new XElement("PremiereDate", series.FirstAired.Value.ToString("yyyy-MM-dd")));
}
//tvShow.Add(new XElement("EndDate", series.EndDate.ToString("yyyy-MM-dd")));
tvShow.Add(new XElement("Rating", (decimal)series.Ratings.Percentage / 10));
tvShow.Add(new XElement("Rating", series.Ratings.Value));
//tvShow.Add(new XElement("VoteCount",
tvShow.Add(new XElement("ProductionYear", series.Year));
//tvShow.Add(new XElement("Website",

View File

@ -171,7 +171,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
var tvShow = new XElement("tvshow");
tvShow.Add(new XElement("title", series.Title));
tvShow.Add(new XElement("rating", (decimal) series.Ratings.Percentage/10));
tvShow.Add(new XElement("rating", series.Ratings.Value));
tvShow.Add(new XElement("plot", series.Overview));
tvShow.Add(new XElement("episodeguide", new XElement("url", episodeGuideUrl)));
tvShow.Add(new XElement("episodeguideurl", episodeGuideUrl));
@ -252,7 +252,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
}
details.Add(new XElement("watched", "false"));
details.Add(new XElement("rating", (decimal)episode.Ratings.Percentage / 10));
details.Add(new XElement("rating", episode.Ratings.Value));
//Todo: get guest stars, writer and director
//details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault()));

View File

@ -0,0 +1,9 @@
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class ActorResource
{
public string Name { get; set; }
public string Character { get; set; }
public string Image { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class EpisodeResource
{
public int SeasonNumber { get; set; }
public int EpisodeNumber { get; set; }
public int? AbsoluteEpisodeNumber { get; set; }
public string Title { get; set; }
public string AirDate { get; set; }
public DateTime? AirDateUtc { get; set; }
public RatingResource Rating { get; set; }
public string Overview { get; set; }
public string Image { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class ImageResource
{
public string CoverType { get; set; }
public string Url { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class RatingResource
{
public int Count { get; set; }
public decimal Value { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class SeasonResource
{
public SeasonResource()
{
Images = new List<ImageResource>();
}
public int SeasonNumber { get; set; }
public List<ImageResource> Images { get; set; }
}
}

View File

@ -0,0 +1,42 @@
using System.Collections.Generic;
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class ShowResource
{
public ShowResource()
{
Actors = new List<ActorResource>();
Genres = new List<string>();
Images = new List<ImageResource>();
Seasons = new List<SeasonResource>();
Episodes = new List<EpisodeResource>();
}
public int TvdbId { get; set; }
public string Title { get; set; }
public string Overview { get; set; }
//public string Language { get; set; }
public string Slug { get; set; }
public string FirstAired { get; set; }
public int? TvRageId { get; set; }
public string Status { get; set; }
public int? Runtime { get; set; }
public TimeOfDayResource TimeOfDay { get; set; }
public string Network { get; set; }
public string ImdbId { get; set; }
public List<ActorResource> Actors { get; set; }
public List<string> Genres { get; set; }
public string ContentRating { get; set; }
public RatingResource Rating { get; set; }
public List<ImageResource> Images { get; set; }
public List<SeasonResource> Seasons { get; set; }
public List<EpisodeResource> Episodes { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{
public class TimeOfDayResource
{
public int Hours { get; set; }
public int Minutes { get; set; }
}
}

View File

@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MetadataSource.SkyHook
{
public class SkyHookProxy : IProvideSeriesInfo
{
private readonly Logger _logger;
private readonly IHttpClient _httpClient;
private readonly HttpRequestBuilder _requestBuilder;
public SkyHookProxy(Logger logger, IHttpClient httpClient)
{
_logger = logger;
_httpClient = httpClient;
_requestBuilder = new HttpRequestBuilder("http://skyhook.sonarr.tv/v1/tvdb/shows/en/");
}
public Tuple<Series, List<Episode>> GetSeriesInfo(int tvdbSeriesId)
{
var httpRequest = _requestBuilder.Build(tvdbSeriesId.ToString());
var httpResponse = _httpClient.Get<ShowResource>(httpRequest);
var episodes = httpResponse.Resource.Episodes.Select(MapEpisode);
var series = MapSeries(httpResponse.Resource);
return new Tuple<Series, List<Episode>>(series, episodes.ToList());
}
private static Series MapSeries(ShowResource show)
{
var series = new Series();
series.TvdbId = show.TvdbId;
if (show.TvRageId.HasValue)
{
series.TvRageId = show.TvRageId.Value;
}
series.ImdbId = show.ImdbId;
series.Title = show.Title;
series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.Title);
series.SortTitle = SeriesTitleNormalizer.Normalize(show.Title, show.TvdbId);
if (show.FirstAired != null)
{
series.FirstAired = DateTime.Parse(show.FirstAired).ToUniversalTime();
series.Year = series.FirstAired.Value.Year;
}
series.Overview = show.Overview;
if (show.Runtime != null)
{
series.Runtime = show.Runtime.Value;
}
series.Network = show.Network;
if (show.TimeOfDay != null)
{
series.AirTime = string.Format("{0:00}:{1:00}", show.TimeOfDay.Hours, show.TimeOfDay.Minutes);
}
series.TitleSlug = show.Slug;
series.Status = MapSeriesStatus(show.Status);
series.Ratings = MapRatings(show.Rating);
series.Genres = show.Genres;
if (show.ContentRating.IsNotNullOrWhiteSpace())
{
series.Certification = show.ContentRating.ToUpper();
}
series.Actors = show.Actors.Select(MapActors).ToList();
series.Seasons = show.Seasons.Select(MapSeason).ToList();
series.Images = show.Images.Select(MapImage).ToList();
return series;
}
private static Actor MapActors(ActorResource arg)
{
var newActor = new Actor
{
Name = arg.Name,
Character = arg.Character,
};
if (arg.Image != null)
{
newActor.Images = new List<MediaCover.MediaCover>
{
new MediaCover.MediaCover(MediaCoverTypes.Headshot, arg.Image)
};
}
return newActor;
}
private static Episode MapEpisode(EpisodeResource oracleEpisode)
{
var episode = new Episode();
episode.Overview = oracleEpisode.Overview;
episode.SeasonNumber = oracleEpisode.SeasonNumber;
episode.EpisodeNumber = oracleEpisode.EpisodeNumber;
episode.AbsoluteEpisodeNumber = oracleEpisode.AbsoluteEpisodeNumber;
episode.Title = oracleEpisode.Title;
episode.AirDate = oracleEpisode.AirDate;
episode.AirDateUtc = oracleEpisode.AirDateUtc;
if (oracleEpisode.Rating != null)
{
episode.Ratings = MapRatings(oracleEpisode.Rating);
}
//Don't include series fanart images as episode screenshot
if (oracleEpisode.Image != null)
{
episode.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Screenshot, oracleEpisode.Image));
}
return episode;
}
private static Season MapSeason(SeasonResource seasonResource)
{
return new Season
{
SeasonNumber = seasonResource.SeasonNumber,
Images = seasonResource.Images.Select(MapImage).ToList()
};
}
private static SeriesStatusType MapSeriesStatus(string status)
{
if (status.Equals("ended", StringComparison.InvariantCultureIgnoreCase))
{
return SeriesStatusType.Ended;
}
return SeriesStatusType.Continuing;
}
private static Ratings MapRatings(RatingResource rating)
{
if (rating == null)
{
return new Ratings();
}
return new Ratings
{
Votes = rating.Count,
Value = rating.Value
};
}
private static MediaCover.MediaCover MapImage(ImageResource arg)
{
return new MediaCover.MediaCover
{
Url = arg.Url,
CoverType = MapCoverType(arg.CoverType)
};
}
private static MediaCoverTypes MapCoverType(string coverType)
{
switch (coverType.ToLower())
{
case "poster":
return MediaCoverTypes.Poster;
case "banner":
return MediaCoverTypes.Banner;
case "fanart":
return MediaCoverTypes.Fanart;
default:
return MediaCoverTypes.Unknown;
}
}
}
}

View File

@ -14,7 +14,7 @@ using TVDBSharp.Models.Enums;
namespace NzbDrone.Core.MetadataSource
{
public class TvDbProxy : ISearchForNewSeries, IProvideSeriesInfo
public class TvDbProxy : ISearchForNewSeries
{
private readonly Logger _logger;
private static readonly Regex CollapseSpaceRegex = new Regex(@"\s+", RegexOptions.Compiled);
@ -215,14 +215,14 @@ namespace NzbDrone.Core.MetadataSource
return phrase;
}
private static Tv.Ratings GetRatings(int ratingCount, double? rating)
private static Tv.Ratings GetRatings(int ratingCount, decimal? rating)
{
var result = new Tv.Ratings { Votes = ratingCount };
if (rating != null)
{
result.Percentage = (int)(rating.Value * 100);
result.Value = rating.Value;
}
return result;

View File

@ -596,6 +596,14 @@
<Compile Include="Messaging\Events\IEventAggregator.cs" />
<Compile Include="Messaging\Events\IHandle.cs" />
<Compile Include="Messaging\IProcessMessage.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ImageResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\RatingResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\SeasonResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ShowResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\TimeOfDayResource.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" />
<Compile Include="MetadataSource\TvDbProxy.cs" />
<Compile Include="MetadataSource\SearchSeriesComparer.cs" />
<Compile Include="MetadataSource\Tvdb\TvdbException.cs" />

View File

@ -5,9 +5,7 @@ namespace NzbDrone.Core.Tv
{
public class Ratings : IEmbeddedDocument
{
public Int32 Percentage { get; set; }
public Int32 Votes { get; set; }
public Int32 Loved { get; set; }
public Int32 Hated { get; set; }
public int Votes { get; set; }
public decimal Value { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using System.Threading;
using System;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Api.Series;
@ -15,12 +16,12 @@ namespace NzbDrone.Integration.Test
[SetUp]
public void Setup()
{
series = GivenSeriesWithEpisodes();
series = GivenSeriesWithEpisodes();
}
private SeriesResource GivenSeriesWithEpisodes()
{
var newSeries = Series.Lookup("archer").First();
var newSeries = Series.Lookup("archer").Single(c => c.TvdbId == 110381);
newSeries.ProfileId = 1;
newSeries.Path = @"C:\Test\Archer".AsOsAgnostic();
@ -34,6 +35,7 @@ namespace NzbDrone.Integration.Test
return newSeries;
}
Console.WriteLine("Waiting for episodes to load.");
Thread.Sleep(1000);
}
}

View File

@ -105,8 +105,8 @@ namespace TVDBSharp.Models
_show.Network = doc.GetSeriesData("Network");
_show.Description = doc.GetSeriesData("Overview");
_show.Rating = string.IsNullOrWhiteSpace(doc.GetSeriesData("Rating"))
? (double?) null
: Convert.ToDouble(doc.GetSeriesData("Rating"),
? (decimal?) null
: Convert.ToDecimal(doc.GetSeriesData("Rating"),
System.Globalization.CultureInfo.InvariantCulture);
_show.RatingCount = string.IsNullOrWhiteSpace(doc.GetSeriesData("RatingCount"))
? 0
@ -182,8 +182,8 @@ namespace TVDBSharp.Models
: Convert.ToInt64(episodeNode.GetXmlData("lastupdated")),
Rating =
string.IsNullOrWhiteSpace(episodeNode.GetXmlData("Rating"))
? (double?) null
: Convert.ToDouble(episodeNode.GetXmlData("Rating"),
? (Decimal?) null
: Convert.ToDecimal(episodeNode.GetXmlData("Rating"),
System.Globalization.CultureInfo.InvariantCulture),
RatingCount =
string.IsNullOrWhiteSpace(episodeNode.GetXmlData("RatingCount"))

View File

@ -66,7 +66,7 @@ namespace TVDBSharp.Models
/// <summary>
/// Average rating as shown on IMDb.
/// </summary>
public double? Rating { get; set; }
public decimal? Rating { get; set; }
/// <summary>
/// Amount of votes cast.

View File

@ -67,7 +67,7 @@ namespace TVDBSharp.Models
/// <summary>
/// Average rating as shown on IMDb.
/// </summary>
public double? Rating { get; set; }
public decimal? Rating { get; set; }
/// <summary>
/// Amount of votes cast.