convert series providers to new system

This commit is contained in:
Luke Pulverenti 2014-02-03 15:51:28 -05:00
parent b7a811992b
commit 48b9f657a4
19 changed files with 1482 additions and 1859 deletions

View File

@ -784,7 +784,8 @@ namespace MediaBrowser.Api.Images
// Validate first
using (var validationStream = new MemoryStream(bytes))
{
using (var image = Image.FromStream(validationStream))
// This will throw an exception if it's not a valid image
using (Image.FromStream(validationStream))
{
}
}

View File

@ -224,6 +224,7 @@ namespace MediaBrowser.Model.Configuration
EnableEpisodeChapterImageExtraction = false;
EnableOtherVideoChapterImageExtraction = false;
EnableAutomaticRestart = true;
EnablePeoplePrefixSubFolders = true;
MinResumePct = 5;
MaxResumePct = 90;

View File

@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.BoxSets
{
/// <summary>
/// Class SeriesProviderFromXml
/// Class BoxSetXmlProvider.
/// </summary>
public class BoxSetXmlProvider : BaseXmlProvider, ILocalMetadataProvider<BoxSet>
{

View File

@ -107,6 +107,8 @@
<Compile Include="Music\ArtistMetadataService.cs" />
<Compile Include="Music\LastfmArtistProvider.cs" />
<Compile Include="Music\MusicBrainzArtistProvider.cs" />
<Compile Include="Omdb\OmdbProvider.cs" />
<Compile Include="Omdb\OmdbSeriesProvider.cs" />
<Compile Include="People\MovieDbPersonImageProvider.cs" />
<Compile Include="Movies\MovieUpdatesPrescanTask.cs" />
<Compile Include="Movies\MovieXmlParser.cs" />
@ -152,22 +154,20 @@
<Compile Include="TV\EpisodeIndexNumberProvider.cs" />
<Compile Include="TV\EpisodeProviderFromXml.cs" />
<Compile Include="TV\EpisodeXmlParser.cs" />
<Compile Include="TV\FanArtTVProvider.cs" />
<Compile Include="TV\FanArtTvUpdatesPrescanTask.cs" />
<Compile Include="TV\FanartSeasonProvider.cs" />
<Compile Include="TV\ManualFanartSeriesProvider.cs" />
<Compile Include="TV\ManualTvdbEpisodeImageProvider.cs" />
<Compile Include="TV\FanartSeriesProvider.cs" />
<Compile Include="TV\SeriesMetadataService.cs" />
<Compile Include="TV\TvdbEpisodeImageProvider.cs" />
<Compile Include="People\TvdbPersonImageProvider.cs" />
<Compile Include="TV\TvdbSeasonImageProvider.cs" />
<Compile Include="TV\ManualTvdbSeriesImageProvider.cs" />
<Compile Include="TV\TvdbSeriesImageProvider.cs" />
<Compile Include="TV\SeasonMetadataService.cs" />
<Compile Include="TV\TvdbEpisodeProvider.cs" />
<Compile Include="TV\TvdbSeriesImageProvider.cs" />
<Compile Include="TV\TvdbSeriesProvider.cs" />
<Compile Include="TV\SeasonXmlProvider.cs" />
<Compile Include="TV\SeriesDynamicInfoProvider.cs" />
<Compile Include="TV\SeriesPostScanTask.cs" />
<Compile Include="TV\SeriesProviderFromXml.cs" />
<Compile Include="TV\SeriesXmlProvider.cs" />
<Compile Include="TV\SeriesXmlParser.cs" />
<Compile Include="TV\TvdbPrescanTask.cs" />
<Compile Include="UserRootFolderNameProvider.cs" />

View File

@ -0,0 +1,200 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Serialization;
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Omdb
{
public class OmdbProvider
{
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
}
public async Task Fetch(BaseItem item, CancellationToken cancellationToken)
{
var imdbId = item.GetProviderId(MetadataProviders.Imdb);
if (string.IsNullOrEmpty(imdbId))
{
return;
}
var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
var url = string.Format("http://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
using (var stream = await _httpClient.Get(new HttpRequestOptions
{
Url = url,
ResourcePool = _resourcePool,
CancellationToken = cancellationToken
}).ConfigureAwait(false))
{
var result = _jsonSerializer.DeserializeFromStream<RootObject>(stream);
var hasCriticRating = item as IHasCriticRating;
if (hasCriticRating != null)
{
// Seeing some bogus RT data on omdb for series, so filter it out here
// RT doesn't even have tv series
int tomatoMeter;
if (!string.IsNullOrEmpty(result.tomatoMeter)
&& int.TryParse(result.tomatoMeter, NumberStyles.Integer, _usCulture, out tomatoMeter)
&& tomatoMeter >= 0)
{
hasCriticRating.CriticRating = tomatoMeter;
}
if (!string.IsNullOrEmpty(result.tomatoConsensus)
&& !string.Equals(result.tomatoConsensus, "n/a", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase))
{
hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus);
}
}
int voteCount;
if (!string.IsNullOrEmpty(result.imdbVotes)
&& int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out voteCount)
&& voteCount >= 0)
{
item.VoteCount = voteCount;
}
float imdbRating;
if (!string.IsNullOrEmpty(result.imdbRating)
&& float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out imdbRating)
&& imdbRating >= 0)
{
item.CommunityRating = imdbRating;
}
if (!string.IsNullOrEmpty(result.Website)
&& !string.Equals(result.Website, "n/a", StringComparison.OrdinalIgnoreCase))
{
item.HomePageUrl = result.Website;
}
ParseAdditionalMetadata(item, result);
}
}
private void ParseAdditionalMetadata(BaseItem item, RootObject result)
{
// Grab series genres because imdb data is better than tvdb. Leave movies alone
// But only do it if english is the preferred language because this data will not be localized
if (!item.LockedFields.Contains(MetadataFields.Genres) &&
ShouldFetchGenres(item) &&
!string.IsNullOrWhiteSpace(result.Genre) &&
!string.Equals(result.Genre, "n/a", StringComparison.OrdinalIgnoreCase))
{
item.Genres.Clear();
foreach (var genre in result.Genre
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(i => i.Trim())
.Where(i => !string.IsNullOrWhiteSpace(i)))
{
item.AddGenre(genre);
}
}
var hasMetascore = item as IHasMetascore;
if (hasMetascore != null)
{
float metascore;
if (!string.IsNullOrEmpty(result.Metascore) && float.TryParse(result.Metascore, NumberStyles.Any, _usCulture, out metascore) && metascore >= 0)
{
hasMetascore.Metascore = metascore;
}
}
var hasAwards = item as IHasAwards;
if (hasAwards != null && !string.IsNullOrEmpty(result.Awards) &&
!string.Equals(result.Awards, "n/a", StringComparison.OrdinalIgnoreCase))
{
hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards);
}
}
private bool ShouldFetchGenres(BaseItem item)
{
var lang = item.GetPreferredMetadataLanguage();
// The data isn't localized and so can only be used for english users
if (!string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase))
{
return false;
}
// Only fetch if other providers didn't get anything
if (item is Trailer)
{
return item.Genres.Count == 0;
}
return item is Series || item is Movie;
}
protected class RootObject
{
public string Title { get; set; }
public string Year { get; set; }
public string Rated { get; set; }
public string Released { get; set; }
public string Runtime { get; set; }
public string Genre { get; set; }
public string Director { get; set; }
public string Writer { get; set; }
public string Actors { get; set; }
public string Plot { get; set; }
public string Poster { get; set; }
public string imdbRating { get; set; }
public string imdbVotes { get; set; }
public string imdbID { get; set; }
public string Type { get; set; }
public string tomatoMeter { get; set; }
public string tomatoImage { get; set; }
public string tomatoRating { get; set; }
public string tomatoReviews { get; set; }
public string tomatoFresh { get; set; }
public string tomatoRotten { get; set; }
public string tomatoConsensus { get; set; }
public string tomatoUserMeter { get; set; }
public string tomatoUserRating { get; set; }
public string tomatoUserReviews { get; set; }
public string DVD { get; set; }
public string BoxOffice { get; set; }
public string Production { get; set; }
public string Website { get; set; }
public string Response { get; set; }
public string Language { get; set; }
public string Country { get; set; }
public string Awards { get; set; }
public string Metascore { get; set; }
}
}
}

View File

@ -0,0 +1,31 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Serialization;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.Omdb
{
public class OmdbSeriesProvider : ICustomMetadataProvider<Series>
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
public OmdbSeriesProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
}
public Task FetchAsync(Series item, CancellationToken cancellationToken)
{
return new OmdbProvider(_jsonSerializer, _httpClient).Fetch(item, cancellationToken);
}
public string Name
{
get { return "OMDb"; }
}
}
}

View File

@ -20,14 +20,14 @@ using System.Xml;
namespace MediaBrowser.Providers.TV
{
public class FanartSeasonImageProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
public class FanartSeasonProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
public FanartSeasonImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
public FanartSeasonProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
_config = config;
_httpClient = httpClient;
@ -78,9 +78,9 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrEmpty(id) && season.IndexNumber.HasValue)
{
await FanArtTvProvider.Current.EnsureSeriesXml(id, cancellationToken).ConfigureAwait(false);
await FanartSeriesProvider.Current.EnsureSeriesXml(id, cancellationToken).ConfigureAwait(false);
var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id);
var xmlPath = FanartSeriesProvider.Current.GetFanartXmlPath(id);
try
{
@ -290,7 +290,7 @@ namespace MediaBrowser.Providers.TV
if (!String.IsNullOrEmpty(tvdbId))
{
// Process images
var imagesXmlPath = FanArtTvProvider.Current.GetFanartXmlPath(tvdbId);
var imagesXmlPath = FanartSeriesProvider.Current.GetFanartXmlPath(tvdbId);
var fileInfo = new FileInfo(imagesXmlPath);

View File

@ -1,331 +0,0 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Providers;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using System.Net;
using MediaBrowser.Providers.Music;
namespace MediaBrowser.Providers.TV
{
class FanArtTvProvider : BaseMetadataProvider
{
protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/series/{0}/{1}/xml/all/1/1";
internal static FanArtTvProvider Current { get; private set; }
/// <summary>
/// Gets the HTTP client.
/// </summary>
/// <value>The HTTP client.</value>
protected IHttpClient HttpClient { get; private set; }
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
public FanArtTvProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
: base(logManager, configurationManager)
{
if (httpClient == null)
{
throw new ArgumentNullException("httpClient");
}
HttpClient = httpClient;
_providerManager = providerManager;
_fileSystem = fileSystem;
Current = this;
}
public override bool Supports(BaseItem item)
{
return item is Series;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.Third; }
}
public override ItemUpdateType ItemUpdateType
{
get
{
return ItemUpdateType.ImageUpdate;
}
}
/// <summary>
/// Needses the refresh internal.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="providerInfo">The provider info.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
{
if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tvdb)))
{
return false;
}
return base.NeedsRefreshInternal(item, providerInfo);
}
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
{
var id = item.GetProviderId(MetadataProviders.Tvdb);
if (!string.IsNullOrEmpty(id))
{
// Process images
var xmlPath = GetFanartXmlPath(id);
var fileInfo = new FileInfo(xmlPath);
return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
}
return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
}
/// <summary>
/// Gets a value indicating whether [refresh on version change].
/// </summary>
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
protected override bool RefreshOnVersionChange
{
get
{
return true;
}
}
/// <summary>
/// Gets the provider version.
/// </summary>
/// <value>The provider version.</value>
protected override string ProviderVersion
{
get
{
return "1";
}
}
/// <summary>
/// Gets the series data path.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <param name="seriesId">The series id.</param>
/// <returns>System.String.</returns>
internal static string GetSeriesDataPath(IApplicationPaths appPaths, string seriesId)
{
var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
return seriesDataPath;
}
/// <summary>
/// Gets the series data path.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <returns>System.String.</returns>
internal static string GetSeriesDataPath(IApplicationPaths appPaths)
{
var dataPath = Path.Combine(appPaths.DataPath, "fanart-tv");
return dataPath;
}
public string GetFanartXmlPath(string tvdbId)
{
var dataPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, tvdbId);
return Path.Combine(dataPath, "fanart.xml");
}
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var seriesId = item.GetProviderId(MetadataProviders.Tvdb);
if (!string.IsNullOrEmpty(seriesId))
{
var xmlPath = GetFanartXmlPath(seriesId);
// Only download the xml if it doesn't already exist. The prescan task will take care of getting updates
if (!File.Exists(xmlPath))
{
await DownloadSeriesXml(seriesId, cancellationToken).ConfigureAwait(false);
}
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartSeriesImageProvider.ProviderName).ConfigureAwait(false);
await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
}
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
/// <summary>
/// Fetches from XML.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="images">The images.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
private async Task FetchFromXml(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
{
var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
if (!item.LockedFields.Contains(MetadataFields.Images))
{
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Primary) && !item.HasImage(ImageType.Primary))
{
await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Logo) && !item.HasImage(ImageType.Logo))
{
await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Art) && !item.HasImage(ImageType.Art))
{
await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Thumb) && !item.HasImage(ImageType.Thumb))
{
await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
{
await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
}
}
if (!item.LockedFields.Contains(MetadataFields.Backdrops))
{
cancellationToken.ThrowIfCancellationRequested();
var backdropLimit = options.GetLimit(ImageType.Backdrop);
if (options.IsEnabled(ImageType.Backdrop) &&
item.BackdropImagePaths.Count < backdropLimit)
{
foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
{
await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
.ConfigureAwait(false);
if (item.BackdropImagePaths.Count >= backdropLimit) break;
}
}
}
}
private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
{
foreach (var image in images.Where(i => i.Type == type))
{
try
{
await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
break;
}
catch (HttpException ex)
{
// Sometimes fanart has bad url's in their xml
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
{
continue;
}
break;
}
}
}
private readonly Task _cachedTask = Task.FromResult(true);
internal Task EnsureSeriesXml(string tvdbId, CancellationToken cancellationToken)
{
var xmlPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, tvdbId);
var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath);
if (fileInfo.Exists)
{
if (ConfigurationManager.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
{
return _cachedTask;
}
}
return DownloadSeriesXml(tvdbId, cancellationToken);
}
/// <summary>
/// Downloads the series XML.
/// </summary>
/// <param name="tvdbId">The TVDB id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
internal async Task DownloadSeriesXml(string tvdbId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tvdbId);
var xmlPath = GetFanartXmlPath(tvdbId);
Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
using (var response = await HttpClient.Get(new HttpRequestOptions
{
Url = url,
ResourcePool = FanartArtistProvider.FanArtResourcePool,
CancellationToken = cancellationToken
}).ConfigureAwait(false))
{
using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
{
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
}
}
}
}
}

View File

@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.TV
return;
}
var path = FanArtTvProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
var path = FanartSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
Directory.CreateDirectory(path);
@ -149,8 +149,8 @@ namespace MediaBrowser.Providers.TV
foreach (var id in list)
{
_logger.Info("Updating series " + id);
await FanArtTvProvider.Current.DownloadSeriesXml(id, cancellationToken).ConfigureAwait(false);
await FanartSeriesProvider.Current.DownloadSeriesXml(id, cancellationToken).ConfigureAwait(false);
numComplete++;
double percent = numComplete;

View File

@ -1,4 +1,6 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@ -6,6 +8,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Music;
using System;
using System.Collections.Generic;
using System.Globalization;
@ -15,20 +18,27 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Providers.Music;
namespace MediaBrowser.Providers.TV
{
public class ManualFanartSeriesImageProvider : IRemoteImageProvider, IHasOrder
public class FanartSeriesProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
public ManualFanartSeriesImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/series/{0}/{1}/xml/all/1/1";
internal static FanartSeriesProvider Current { get; private set; }
public FanartSeriesProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
_config = config;
_httpClient = httpClient;
_fileSystem = fileSystem;
Current = this;
}
public string Name
@ -66,7 +76,7 @@ namespace MediaBrowser.Providers.TV
return images.Where(i => i.Type == imageType);
}
public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List<RemoteImageInfo>();
@ -76,7 +86,9 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrEmpty(id))
{
var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id);
await EnsureSeriesXml(id, cancellationToken).ConfigureAwait(false);
var xmlPath = GetFanartXmlPath(id);
try
{
@ -93,7 +105,7 @@ namespace MediaBrowser.Providers.TV
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
// Sort first by width to prioritize HD versions
list = list.OrderByDescending(i => i.Width ?? 0)
return list.OrderByDescending(i => i.Width ?? 0)
.ThenByDescending(i =>
{
if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
@ -114,10 +126,7 @@ namespace MediaBrowser.Providers.TV
return 0;
})
.ThenByDescending(i => i.CommunityRating ?? 0)
.ThenByDescending(i => i.VoteCount ?? 0)
.ToList();
return Task.FromResult<IEnumerable<RemoteImageInfo>>(list);
.ThenByDescending(i => i.VoteCount ?? 0);
}
private void AddImages(List<RemoteImageInfo> list, string xmlPath, CancellationToken cancellationToken)
@ -333,5 +342,102 @@ namespace MediaBrowser.Providers.TV
ResourcePool = FanartArtistProvider.FanArtResourcePool
});
}
/// <summary>
/// Gets the series data path.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <param name="seriesId">The series id.</param>
/// <returns>System.String.</returns>
internal static string GetSeriesDataPath(IApplicationPaths appPaths, string seriesId)
{
var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
return seriesDataPath;
}
/// <summary>
/// Gets the series data path.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <returns>System.String.</returns>
internal static string GetSeriesDataPath(IApplicationPaths appPaths)
{
var dataPath = Path.Combine(appPaths.DataPath, "fanart-tv");
return dataPath;
}
public string GetFanartXmlPath(string tvdbId)
{
var dataPath = GetSeriesDataPath(_config.ApplicationPaths, tvdbId);
return Path.Combine(dataPath, "fanart.xml");
}
private readonly Task _cachedTask = Task.FromResult(true);
internal Task EnsureSeriesXml(string tvdbId, CancellationToken cancellationToken)
{
var xmlPath = GetSeriesDataPath(_config.ApplicationPaths, tvdbId);
var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath);
if (fileInfo.Exists)
{
if (_config.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
{
return _cachedTask;
}
}
return DownloadSeriesXml(tvdbId, cancellationToken);
}
/// <summary>
/// Downloads the series XML.
/// </summary>
/// <param name="tvdbId">The TVDB id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
internal async Task DownloadSeriesXml(string tvdbId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tvdbId);
var xmlPath = GetFanartXmlPath(tvdbId);
Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
using (var response = await _httpClient.Get(new HttpRequestOptions
{
Url = url,
ResourcePool = FanartArtistProvider.FanArtResourcePool,
CancellationToken = cancellationToken
}).ConfigureAwait(false))
{
using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
{
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
}
}
}
public bool HasChanged(IHasMetadata item, DateTime date)
{
var tvdbId = item.GetProviderId(MetadataProviders.Tvdb);
if (!String.IsNullOrEmpty(tvdbId))
{
// Process images
var imagesXmlPath = GetFanartXmlPath(tvdbId);
var fileInfo = new FileInfo(imagesXmlPath);
return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date;
}
return false;
}
}
}

View File

@ -1,335 +0,0 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace MediaBrowser.Providers.TV
{
public class ManualTvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public ManualTvdbSeriesImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
{
_config = config;
_httpClient = httpClient;
}
public string Name
{
get { return ProviderName; }
}
public static string ProviderName
{
get { return "TheTVDB"; }
}
public bool Supports(IHasImages item)
{
return item is Series;
}
public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
{
return new List<ImageType>
{
ImageType.Primary,
ImageType.Banner,
ImageType.Backdrop
};
}
public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var series = (Series)item;
var seriesId = series.GetProviderId(MetadataProviders.Tvdb);
if (!string.IsNullOrEmpty(seriesId))
{
// Process images
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);
var path = Path.Combine(seriesDataPath, "banners.xml");
try
{
var result = GetImages(path, item.GetPreferredMetadataLanguage(), cancellationToken);
return Task.FromResult(result);
}
catch (FileNotFoundException)
{
// No tvdb data yet. Don't blow up
}
}
return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
}
private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, CancellationToken cancellationToken)
{
var settings = new XmlReaderSettings
{
CheckCharacters = false,
IgnoreProcessingInstructions = true,
IgnoreComments = true,
ValidationType = ValidationType.None
};
var list = new List<RemoteImageInfo>();
using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8))
{
// Use XmlReader for best performance
using (var reader = XmlReader.Create(streamReader, settings))
{
reader.MoveToContent();
// Loop through each element
while (reader.Read())
{
cancellationToken.ThrowIfCancellationRequested();
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "Banner":
{
using (var subtree = reader.ReadSubtree())
{
AddImage(subtree, list);
}
break;
}
default:
reader.Skip();
break;
}
}
}
}
}
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
{
if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
{
return 3;
}
if (!isLanguageEn)
{
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
{
return 2;
}
}
if (string.IsNullOrEmpty(i.Language))
{
return isLanguageEn ? 3 : 2;
}
return 0;
})
.ThenByDescending(i => i.CommunityRating ?? 0)
.ThenByDescending(i => i.VoteCount ?? 0)
.ToList();
}
private void AddImage(XmlReader reader, List<RemoteImageInfo> images)
{
reader.MoveToContent();
string bannerType = null;
string url = null;
int? bannerSeason = null;
int? width = null;
int? height = null;
string language = null;
double? rating = null;
int? voteCount = null;
string thumbnailUrl = null;
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "Rating":
{
var val = reader.ReadElementContentAsString() ?? string.Empty;
double rval;
if (double.TryParse(val, NumberStyles.Any, _usCulture, out rval))
{
rating = rval;
}
break;
}
case "RatingCount":
{
var val = reader.ReadElementContentAsString() ?? string.Empty;
int rval;
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval))
{
voteCount = rval;
}
break;
}
case "Language":
{
language = reader.ReadElementContentAsString() ?? string.Empty;
break;
}
case "ThumbnailPath":
{
thumbnailUrl = reader.ReadElementContentAsString() ?? string.Empty;
break;
}
case "BannerType":
{
bannerType = reader.ReadElementContentAsString() ?? string.Empty;
break;
}
case "BannerPath":
{
url = reader.ReadElementContentAsString() ?? string.Empty;
break;
}
case "BannerType2":
{
var bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
// Sometimes the resolution is stuffed in here
var resolutionParts = bannerType2.Split('x');
if (resolutionParts.Length == 2)
{
int rval;
if (int.TryParse(resolutionParts[0], NumberStyles.Integer, _usCulture, out rval))
{
width = rval;
}
if (int.TryParse(resolutionParts[1], NumberStyles.Integer, _usCulture, out rval))
{
height = rval;
}
}
break;
}
case "Season":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
bannerSeason = int.Parse(val);
}
break;
}
default:
reader.Skip();
break;
}
}
}
if (!string.IsNullOrEmpty(url) && !bannerSeason.HasValue)
{
var imageInfo = new RemoteImageInfo
{
RatingType = RatingType.Score,
CommunityRating = rating,
VoteCount = voteCount,
Url = TVUtils.BannerUrl + url,
ProviderName = Name,
Language = language,
Width = width,
Height = height
};
if (!string.IsNullOrEmpty(thumbnailUrl))
{
imageInfo.ThumbnailUrl = TVUtils.BannerUrl + thumbnailUrl;
}
if (string.Equals(bannerType, "poster", StringComparison.OrdinalIgnoreCase))
{
imageInfo.Type = ImageType.Primary;
images.Add(imageInfo);
}
else if (string.Equals(bannerType, "series", StringComparison.OrdinalIgnoreCase))
{
imageInfo.Type = ImageType.Banner;
images.Add(imageInfo);
}
else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
{
imageInfo.Type = ImageType.Backdrop;
images.Add(imageInfo);
}
}
}
public int Order
{
get { return 0; }
}
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClient.GetResponse(new HttpRequestOptions
{
CancellationToken = cancellationToken,
Url = url,
ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
});
}
}
}

View File

@ -1,45 +0,0 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV
{
public class SeriesDynamicInfoProvider : BaseMetadataProvider, IDynamicInfoProvider
{
public SeriesDynamicInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
: base(logManager, configurationManager)
{
}
public override bool Supports(BaseItem item)
{
return item is Series;
}
public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
var series = (Series)item;
var episodes = series.RecursiveChildren
.OfType<Episode>()
.ToList();
series.DateLastEpisodeAdded = episodes.Select(i => i.DateCreated)
.OrderByDescending(i => i)
.FirstOrDefault();
// Don't save to the db
return FalseTaskResult;
}
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.Last; }
}
}
}

View File

@ -0,0 +1,66 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV
{
public class SeriesMetadataService : MetadataService<Series, ItemId>
{
private readonly ILibraryManager _libraryManager;
public SeriesMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
: base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Merges the specified source.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="lockedFields">The locked fields.</param>
/// <param name="replaceData">if set to <c>true</c> [replace data].</param>
/// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
protected override void MergeData(Series source, Series target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
protected override Task SaveItem(Series item, ItemUpdateType reason, CancellationToken cancellationToken)
{
return _libraryManager.UpdateItem(item, reason, cancellationToken);
}
protected override ItemUpdateType AfterMetadataRefresh(Series item)
{
var updateType = base.AfterMetadataRefresh(item);
var episodes = item.RecursiveChildren
.OfType<Episode>()
.ToList();
var dateLastEpisodeAdded = item.DateLastEpisodeAdded;
item.DateLastEpisodeAdded = episodes.Select(i => i.DateCreated)
.OrderByDescending(i => i)
.FirstOrDefault();
if (dateLastEpisodeAdded != item.DateLastEpisodeAdded)
{
updateType = updateType | ItemUpdateType.MetadataImport;
}
return updateType;
}
}
}

View File

@ -1,95 +0,0 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV
{
/// <summary>
/// Class SeriesProviderFromXml
/// </summary>
public class SeriesProviderFromXml : BaseMetadataProvider
{
private readonly IFileSystem _fileSystem;
public SeriesProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
: base(logManager, configurationManager)
{
_fileSystem = fileSystem;
}
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
return item is Series && item.LocationType == LocationType.FileSystem;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
private const string XmlFileName = "series.xml";
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
{
var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
if (xml == null)
{
return false;
}
return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
}
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="item">The item.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
if (metadataFile != null)
{
var path = metadataFile.FullName;
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
new SeriesXmlParser(Logger).Fetch((Series)item, path, cancellationToken);
}
finally
{
XmlParsingResourcePool.Release();
}
}
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
}
}

View File

@ -0,0 +1,62 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV
{
/// <summary>
/// Class SeriesProviderFromXml
/// </summary>
public class SeriesXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Series>
{
private readonly ILogger _logger;
public SeriesXmlProvider(IFileSystem fileSystem, ILogger logger)
: base(fileSystem)
{
_logger = logger;
}
public async Task<MetadataResult<Series>> GetMetadata(string path, CancellationToken cancellationToken)
{
path = GetXmlFile(path).FullName;
var result = new MetadataResult<Series>();
await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var person = new Series();
new SeriesXmlParser(_logger).Fetch(person, path, cancellationToken);
result.HasMetadata = true;
result.Item = person;
}
catch (FileNotFoundException)
{
result.HasMetadata = false;
}
finally
{
XmlParsingResourcePool.Release();
}
return result;
}
public string Name
{
get { return "Media Browser Xml"; }
}
protected override FileInfo GetXmlFile(string path)
{
return new FileInfo(Path.Combine(path, "series.xml"));
}
}
}

View File

@ -17,13 +17,13 @@ using System.Xml;
namespace MediaBrowser.Providers.TV
{
public class ManualTvdbEpisodeImageProvider : IRemoteImageProvider
public class TvdbEpisodeImageProvider : IRemoteImageProvider
{
private readonly IServerConfigurationManager _config;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IHttpClient _httpClient;
public ManualTvdbEpisodeImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
public TvdbEpisodeImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
{
_config = config;
_httpClient = httpClient;

View File

@ -5,211 +5,353 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Providers;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace MediaBrowser.Providers.TV
{
public class TvdbSeriesImageProvider : BaseMetadataProvider
public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
{
/// <summary>
/// Gets the HTTP client.
/// </summary>
/// <value>The HTTP client.</value>
protected IHttpClient HttpClient { get; private set; }
/// <summary>
/// The _provider manager
/// </summary>
private readonly IProviderManager _providerManager;
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="TvdbSeriesImageProvider"/> class.
/// </summary>
/// <param name="httpClient">The HTTP client.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="providerManager">The provider manager.</param>
/// <exception cref="System.ArgumentNullException">httpClient</exception>
public TvdbSeriesImageProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
: base(logManager, configurationManager)
public TvdbSeriesImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
if (httpClient == null)
{
throw new ArgumentNullException("httpClient");
}
HttpClient = httpClient;
_providerManager = providerManager;
_config = config;
_httpClient = httpClient;
_fileSystem = fileSystem;
}
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
public string Name
{
get { return ProviderName; }
}
public static string ProviderName
{
get { return "TheTVDB"; }
}
public bool Supports(IHasImages item)
{
return item is Series;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
{
// Run after fanart
get { return MetadataProviderPriority.Fourth; }
}
/// <summary>
/// Gets a value indicating whether [requires internet].
/// </summary>
/// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
public override bool RequiresInternet
{
get
return new List<ImageType>
{
return true;
}
ImageType.Primary,
ImageType.Banner,
ImageType.Backdrop
};
}
public override ItemUpdateType ItemUpdateType
public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
get
{
return ItemUpdateType.ImageUpdate;
}
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
/// <summary>
/// Gets a value indicating whether [refresh on version change].
/// </summary>
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
protected override bool RefreshOnVersionChange
public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
get
{
return true;
}
}
/// <summary>
/// Gets the provider version.
/// </summary>
/// <value>The provider version.</value>
protected override string ProviderVersion
{
get
{
return "1";
}
}
protected override DateTime CompareDate(BaseItem item)
{
var seriesId = item.GetProviderId(MetadataProviders.Tvdb);
var series = (Series)item;
var seriesId = series.GetProviderId(MetadataProviders.Tvdb);
if (!string.IsNullOrEmpty(seriesId))
{
var language = item.GetPreferredMetadataLanguage();
await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesId, language, cancellationToken).ConfigureAwait(false);
// Process images
var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);
var imagesFileInfo = new FileInfo(imagesXmlPath);
var path = Path.Combine(seriesDataPath, "banners.xml");
if (imagesFileInfo.Exists)
try
{
return _fileSystem.GetLastWriteTimeUtc(imagesFileInfo);
return GetImages(path, language, cancellationToken);
}
catch (FileNotFoundException)
{
// No tvdb data yet. Don't blow up
}
}
return base.CompareDate(item);
return new RemoteImageInfo[] { };
}
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, CancellationToken cancellationToken)
{
var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count >= options.GetLimit(ImageType.Backdrop))
var settings = new XmlReaderSettings
{
return false;
}
return base.NeedsRefreshInternal(item, providerInfo);
}
CheckCharacters = false,
IgnoreProcessingInstructions = true,
IgnoreComments = true,
ValidationType = ValidationType.None
};
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="item">The item.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var list = new List<RemoteImageInfo>();
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualTvdbSeriesImageProvider.ProviderName).ConfigureAwait(false);
const int backdropLimit = 1;
await DownloadImages(item, images.ToList(), backdropLimit, cancellationToken).ConfigureAwait(false);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, int backdropLimit, CancellationToken cancellationToken)
{
var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
if (!item.LockedFields.Contains(MetadataFields.Images))
using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8))
{
if (!item.HasImage(ImageType.Primary))
// Use XmlReader for best performance
using (var reader = XmlReader.Create(streamReader, settings))
{
var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
reader.MoveToContent();
if (image != null)
// Loop through each element
while (reader.Read())
{
await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
.ConfigureAwait(false);
}
}
cancellationToken.ThrowIfCancellationRequested();
if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
{
var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
if (image != null)
{
await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
.ConfigureAwait(false);
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "Banner":
{
using (var subtree = reader.ReadSubtree())
{
AddImage(subtree, list);
}
break;
}
default:
reader.Skip();
break;
}
}
}
}
}
if (options.IsEnabled(ImageType.Backdrop) && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
{
foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop &&
(!i.Width.HasValue ||
i.Width.Value >= options.GetMinWidth(ImageType.Backdrop))))
if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
{
var url = backdrop.Url;
return 3;
}
if (!isLanguageEn)
{
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
{
return 2;
}
}
if (string.IsNullOrEmpty(i.Language))
{
return isLanguageEn ? 3 : 2;
}
return 0;
})
.ThenByDescending(i => i.CommunityRating ?? 0)
.ThenByDescending(i => i.VoteCount ?? 0)
.ToList();
}
await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, null, cancellationToken).ConfigureAwait(false);
private void AddImage(XmlReader reader, List<RemoteImageInfo> images)
{
reader.MoveToContent();
if (item.BackdropImagePaths.Count >= backdropLimit) break;
string bannerType = null;
string url = null;
int? bannerSeason = null;
int? width = null;
int? height = null;
string language = null;
double? rating = null;
int? voteCount = null;
string thumbnailUrl = null;
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "Rating":
{
var val = reader.ReadElementContentAsString() ?? string.Empty;
double rval;
if (double.TryParse(val, NumberStyles.Any, _usCulture, out rval))
{
rating = rval;
}
break;
}
case "RatingCount":
{
var val = reader.ReadElementContentAsString() ?? string.Empty;
int rval;
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval))
{
voteCount = rval;
}
break;
}
case "Language":
{
language = reader.ReadElementContentAsString() ?? string.Empty;
break;
}
case "ThumbnailPath":
{
thumbnailUrl = reader.ReadElementContentAsString() ?? string.Empty;
break;
}
case "BannerType":
{
bannerType = reader.ReadElementContentAsString() ?? string.Empty;
break;
}
case "BannerPath":
{
url = reader.ReadElementContentAsString() ?? string.Empty;
break;
}
case "BannerType2":
{
var bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
// Sometimes the resolution is stuffed in here
var resolutionParts = bannerType2.Split('x');
if (resolutionParts.Length == 2)
{
int rval;
if (int.TryParse(resolutionParts[0], NumberStyles.Integer, _usCulture, out rval))
{
width = rval;
}
if (int.TryParse(resolutionParts[1], NumberStyles.Integer, _usCulture, out rval))
{
height = rval;
}
}
break;
}
case "Season":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
bannerSeason = int.Parse(val);
}
break;
}
default:
reader.Skip();
break;
}
}
}
if (!string.IsNullOrEmpty(url) && !bannerSeason.HasValue)
{
var imageInfo = new RemoteImageInfo
{
RatingType = RatingType.Score,
CommunityRating = rating,
VoteCount = voteCount,
Url = TVUtils.BannerUrl + url,
ProviderName = Name,
Language = language,
Width = width,
Height = height
};
if (!string.IsNullOrEmpty(thumbnailUrl))
{
imageInfo.ThumbnailUrl = TVUtils.BannerUrl + thumbnailUrl;
}
if (string.Equals(bannerType, "poster", StringComparison.OrdinalIgnoreCase))
{
imageInfo.Type = ImageType.Primary;
images.Add(imageInfo);
}
else if (string.Equals(bannerType, "series", StringComparison.OrdinalIgnoreCase))
{
imageInfo.Type = ImageType.Banner;
images.Add(imageInfo);
}
else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
{
imageInfo.Type = ImageType.Backdrop;
images.Add(imageInfo);
}
}
}
public int Order
{
get { return 0; }
}
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClient.GetResponse(new HttpRequestOptions
{
CancellationToken = cancellationToken,
Url = url,
ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
});
}
public bool HasChanged(IHasMetadata item, DateTime date)
{
var tvdbId = item.GetProviderId(MetadataProviders.Tvdb);
if (!String.IsNullOrEmpty(tvdbId))
{
// Process images
var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, tvdbId), "banners.xml");
var fileInfo = new FileInfo(imagesXmlPath);
return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date;
}
return false;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -208,7 +208,6 @@ namespace MediaBrowser.Server.Implementations.Library
/// <param name="prescanTasks">The prescan tasks.</param>
/// <param name="postscanTasks">The postscan tasks.</param>
/// <param name="peoplePrescanTasks">The people prescan tasks.</param>
/// <param name="savers">The savers.</param>
public void AddParts(IEnumerable<IResolverIgnoreRule> rules,
IEnumerable<IVirtualFolderCreator> pluginFolders,
IEnumerable<IItemResolver> resolvers,
@ -277,7 +276,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <param name="configuration">The configuration.</param>
private void RecordConfigurationValues(ServerConfiguration configuration)
{
_seasonZeroDisplayName = ConfigurationManager.Configuration.SeasonZeroDisplayName;
_seasonZeroDisplayName = configuration.SeasonZeroDisplayName;
_itemsByNamePath = ConfigurationManager.ApplicationPaths.ItemsByNamePath;
}
@ -309,8 +308,10 @@ namespace MediaBrowser.Server.Implementations.Library
await UpdateSeasonZeroNames(newSeasonZeroName, CancellationToken.None).ConfigureAwait(false);
}
// Any number of configuration settings could change the way the library is refreshed, so do that now
_taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
if (seasonZeroNameChanged || ibnPathChanged)
{
_taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
}
});
}