Some of it works???

This commit is contained in:
Claus Vium 2019-01-30 21:23:23 +01:00
parent 9729ae52a3
commit c2202be0f8
9 changed files with 1082 additions and 2354 deletions

View File

@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// The TVDB API key /// The TVDB API key
/// </summary> /// </summary>
public static readonly string TvdbApiKey = "72930AE1CB7E2DB3"; public static readonly string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
public static readonly string TvdbBaseUrl = "https://www.thetvdb.com/"; public static readonly string TvdbBaseUrl = "https://www.thetvdb.com/";
/// <summary> /// <summary>
/// The banner URL /// The banner URL

View File

@ -80,22 +80,9 @@ namespace MediaBrowser.Providers.People
private RemoteImageInfo GetImageFromSeriesData(Series series, string personName, CancellationToken cancellationToken) private RemoteImageInfo GetImageFromSeriesData(Series series, string personName, CancellationToken cancellationToken)
{ {
var tvdbPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds); //var actorsResult = await _tvDbClient.Series.GetActorsAsync(Convert.ToInt32(tvdbId), cancellationToken);
var actorXmlPath = Path.Combine(tvdbPath, "actors.xml"); return null; // GetImageInfo(actorXmlPath, personName, cancellationToken);
try
{
return GetImageInfo(actorXmlPath, personName, cancellationToken);
}
catch (FileNotFoundException)
{
return null;
}
catch (IOException)
{
return null;
}
} }
private RemoteImageInfo GetImageInfo(string xmlFile, string personName, CancellationToken cancellationToken) private RemoteImageInfo GetImageInfo(string xmlFile, string personName, CancellationToken cancellationToken)

View File

@ -44,27 +44,15 @@ namespace MediaBrowser.Providers.TV
public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken) public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken)
{ {
// TODO cvium fixme wtfisthisandwhydoesitrunwhenoptionisdisabled
return true;
var tvdbId = series.GetProviderId(MetadataProviders.Tvdb); var tvdbId = series.GetProviderId(MetadataProviders.Tvdb);
// Todo: Support series by imdb id // Todo: Support series by imdb id
var seriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var seriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
seriesProviderIds[MetadataProviders.Tvdb.ToString()] = tvdbId; seriesProviderIds[MetadataProviders.Tvdb.ToString()] = tvdbId;
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds); var episodeFiles = _fileSystem.GetFilePaths("")
// Doesn't have required provider id's
if (string.IsNullOrWhiteSpace(seriesDataPath))
{
return false;
}
// Check this in order to avoid logging an exception due to directory not existing
if (!Directory.Exists(seriesDataPath))
{
return false;
}
var episodeFiles = _fileSystem.GetFilePaths(seriesDataPath)
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase)) .Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
.Select(Path.GetFileNameWithoutExtension) .Select(Path.GetFileNameWithoutExtension)
.Where(i => i.StartsWith("episode-", StringComparison.OrdinalIgnoreCase)) .Where(i => i.StartsWith("episode-", StringComparison.OrdinalIgnoreCase))
@ -118,7 +106,7 @@ namespace MediaBrowser.Providers.TV
if (addNewItems && series.IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(series), TvdbSeriesProvider.Current.Name)) if (addNewItems && series.IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(series), TvdbSeriesProvider.Current.Name))
{ {
hasNewEpisodes = await AddMissingEpisodes(series, allRecursiveChildren, addMissingEpisodes, seriesDataPath, episodeLookup, cancellationToken) hasNewEpisodes = await AddMissingEpisodes(series, allRecursiveChildren, addMissingEpisodes, "", episodeLookup, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
} }

View File

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -11,23 +10,23 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using TvDbSharper;
using TvDbSharper.Dto;
namespace MediaBrowser.Providers.TV.TheTVDB namespace MediaBrowser.Providers.TV.TheTVDB
{ {
public class TvdbEpisodeImageProvider : IRemoteImageProvider public class TvdbEpisodeImageProvider : IRemoteImageProvider
{ {
private readonly IServerConfigurationManager _config;
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem; private readonly TvDbClient _tvDbClient;
public TvdbEpisodeImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem) public TvdbEpisodeImageProvider(IHttpClient httpClient)
{ {
_config = config;
_httpClient = httpClient; _httpClient = httpClient;
_fileSystem = fileSystem; _tvDbClient = new TvDbClient();
_tvDbClient.Authentication.AuthenticateAsync(TVUtils.TvdbApiKey);
} }
public string Name => "TheTVDB"; public string Name => "TheTVDB";
@ -45,113 +44,44 @@ namespace MediaBrowser.Providers.TV.TheTVDB
}; };
} }
public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{ {
var episode = (Episode)item; var episode = (Episode)item;
var series = episode.Series; var series = episode.Series;
if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
{ {
var tvdbId = episode.GetProviderId(MetadataProviders.Tvdb);
// Process images // Process images
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds); var episodeResult = await _tvDbClient.Episodes.GetAsync(Convert.ToInt32(tvdbId), cancellationToken);
var nodes = TvdbEpisodeProvider.Current.GetEpisodeXmlNodes(seriesDataPath, episode.GetLookupInfo()); var image = GetImageInfo(episodeResult.Data);
return new List<RemoteImageInfo>
var result = nodes.Select(i => GetImageInfo(i, cancellationToken)) {
.Where(i => i != null) image
.ToList(); };
return Task.FromResult<IEnumerable<RemoteImageInfo>>(result);
} }
return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { }); return new RemoteImageInfo[] { };
} }
private RemoteImageInfo GetImageInfo(XmlReader reader, CancellationToken cancellationToken) private RemoteImageInfo GetImageInfo(EpisodeRecord episode)
{ {
var height = 225; var height = 225;
var width = 400; var width = 400;
var url = string.Empty; var url = string.Empty;
// Use XmlReader for best performance if (string.IsNullOrEmpty(episode.Filename))
using (reader)
{
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
cancellationToken.ThrowIfCancellationRequested();
switch (reader.Name)
{
case "thumb_width":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
// int.TryParse is local aware, so it can be probamatic, force us culture
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
{
width = rval;
}
}
break;
}
case "thumb_height":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
// int.TryParse is local aware, so it can be probamatic, force us culture
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
{
height = rval;
}
}
break;
}
case "filename":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
url = TVUtils.BannerUrl + val;
}
break;
}
default:
{
reader.Skip();
break;
}
}
}
else
{
reader.Read();
}
}
}
if (string.IsNullOrEmpty(url))
{ {
return null; return null;
} }
return new RemoteImageInfo return new RemoteImageInfo
{ {
Width = width, Width = Convert.ToInt32(episode.ThumbWidth),
Height = height, Height = Convert.ToInt32(episode.ThumbHeight),
ProviderName = Name, ProviderName = Name,
Url = url, Url = TVUtils.BannerUrl + episode.Filename,
Type = ImageType.Primary Type = ImageType.Primary
}; };
} }

File diff suppressed because it is too large Load Diff

View File

@ -80,7 +80,9 @@ namespace MediaBrowser.Providers.TV.TheTVDB
/// <returns>Task.</returns> /// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{ {
var path = TvdbSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths); return;
var path = "";
//var path = TvdbSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
@ -392,7 +394,8 @@ namespace MediaBrowser.Providers.TV.TheTVDB
Directory.CreateDirectory(seriesDataPath); Directory.CreateDirectory(seriesDataPath);
return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), null, null, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken); //return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), null, null, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken);
return Task.CompletedTask;
} }
} }
} }

View File

@ -1,23 +1,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Xml; using TvDbSharper;
using TvDbSharper.Dto;
using RatingType = MediaBrowser.Model.Dto.RatingType;
namespace MediaBrowser.Providers.TV.TheTVDB namespace MediaBrowser.Providers.TV.TheTVDB
{ {
@ -25,17 +21,14 @@ namespace MediaBrowser.Providers.TV.TheTVDB
{ {
private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem; private readonly TvDbClient _tvDbClient;
private readonly IXmlReaderSettingsFactory _xmlSettings;
public TvdbSeasonImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlSettings) public TvdbSeasonImageProvider(IHttpClient httpClient)
{ {
_config = config;
_httpClient = httpClient; _httpClient = httpClient;
_fileSystem = fileSystem; _tvDbClient = new TvDbClient();
_xmlSettings = xmlSettings; _tvDbClient.Authentication.AuthenticateAsync(TVUtils.TvdbApiKey);
} }
public string Name => ProviderName; public string Name => ProviderName;
@ -62,91 +55,68 @@ namespace MediaBrowser.Providers.TV.TheTVDB
var season = (Season)item; var season = (Season)item;
var series = season.Series; var series = season.Series;
if (series != null && season.IndexNumber.HasValue && TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
{ {
var seriesProviderIds = series.ProviderIds;
var seasonNumber = season.IndexNumber.Value;
var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.Name, series.ProductionYear, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(seriesDataPath))
{
var path = Path.Combine(seriesDataPath, "banners.xml");
try
{
return GetImages(path, item.GetPreferredMetadataLanguage(), seasonNumber, _xmlSettings, _fileSystem, cancellationToken);
}
catch (FileNotFoundException)
{
// No tvdb data yet. Don't blow up
}
catch (IOException)
{
// No tvdb data yet. Don't blow up
}
}
}
return new RemoteImageInfo[] { }; return new RemoteImageInfo[] { };
} }
internal static IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, int seasonNumber, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem, CancellationToken cancellationToken) var seasonNumber = season.IndexNumber.Value;
var language = item.GetPreferredMetadataLanguage();
_tvDbClient.AcceptedLanguage = language;
var remoteImages = new List<RemoteImageInfo>();
var keyTypes = new[] {KeyType.Season, KeyType.Seasonwide, KeyType.Fanart};
// TODO error handling
foreach (KeyType keyType in keyTypes)
{ {
var settings = xmlReaderSettingsFactory.Create(false); var imageQuery = new ImagesQuery
{
KeyType = keyType,
SubKey = seasonNumber.ToString()
};
var imageResults =
await _tvDbClient.Series.GetImagesAsync(Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb)), imageQuery, cancellationToken);
settings.CheckCharacters = false; remoteImages.AddRange(GetImages(imageResults.Data, language));
settings.IgnoreProcessingInstructions = true; }
settings.IgnoreComments = true;
return remoteImages;
}
private static IEnumerable<RemoteImageInfo> GetImages(Image[] images, string preferredLanguage)
{
var list = new List<RemoteImageInfo>(); var list = new List<RemoteImageInfo>();
using (var fileStream = fileSystem.GetFileStream(xmlPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read)) foreach (Image image in images)
{ {
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8)) var resolution = image.Resolution.Split('x');
var imageInfo = new RemoteImageInfo
{ {
// Use XmlReader for best performance RatingType = RatingType.Score,
using (var reader = XmlReader.Create(streamReader, settings)) CommunityRating = (double?)image.RatingsInfo.Average,
{ VoteCount = image.RatingsInfo.Count,
reader.MoveToContent(); Url = TVUtils.BannerUrl + image.FileName,
reader.Read(); ProviderName = ProviderName,
// TODO Language = image.LanguageId,
Width = Convert.ToInt32(resolution[0]),
Height = Convert.ToInt32(resolution[1]),
ThumbnailUrl = TVUtils.BannerUrl + image.Thumbnail
};
// Loop through each element if (string.Equals(image.KeyType, "season", StringComparison.OrdinalIgnoreCase))
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{ {
cancellationToken.ThrowIfCancellationRequested(); imageInfo.Type = ImageType.Primary;
}
if (reader.NodeType == XmlNodeType.Element) else if (string.Equals(image.KeyType, "seasonwide", StringComparison.OrdinalIgnoreCase))
{ {
switch (reader.Name) imageInfo.Type = ImageType.Banner;
}
else if (string.Equals(image.KeyType, "fanart", StringComparison.OrdinalIgnoreCase))
{ {
case "Banner": imageInfo.Type = ImageType.Backdrop;
{
if (reader.IsEmptyElement)
{
reader.Read();
continue;
}
using (var subtree = reader.ReadSubtree())
{
AddImage(subtree, list, seasonNumber);
}
break;
}
default:
reader.Skip();
break;
}
}
else
{
reader.Read();
}
}
}
}
} }
list.Add(imageInfo);
}
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i => return list.OrderByDescending(i =>
@ -155,6 +125,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
{ {
return 3; return 3;
} }
if (!isLanguageEn) if (!isLanguageEn)
{ {
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
@ -162,177 +133,18 @@ namespace MediaBrowser.Providers.TV.TheTVDB
return 2; return 2;
} }
} }
if (string.IsNullOrEmpty(i.Language)) if (string.IsNullOrEmpty(i.Language))
{ {
return isLanguageEn ? 3 : 2; return isLanguageEn ? 3 : 2;
} }
return 0; return 0;
}) })
.ThenByDescending(i => i.CommunityRating ?? 0) .ThenByDescending(i => i.CommunityRating ?? 0)
.ThenByDescending(i => i.VoteCount ?? 0); .ThenByDescending(i => i.VoteCount ?? 0);
} }
private static void AddImage(XmlReader reader, List<RemoteImageInfo> images, int seasonNumber)
{
reader.MoveToContent();
string bannerType = null;
string bannerType2 = 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;
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "Rating":
{
var val = reader.ReadElementContentAsString() ?? string.Empty;
if (double.TryParse(val, NumberStyles.Any, UsCulture, out var rval))
{
rating = rval;
}
break;
}
case "RatingCount":
{
var val = reader.ReadElementContentAsString() ?? string.Empty;
if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var 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 "BannerType2":
{
bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
// Sometimes the resolution is stuffed in here
var resolutionParts = bannerType2.Split('x');
if (resolutionParts.Length == 2)
{
if (int.TryParse(resolutionParts[0], NumberStyles.Integer, UsCulture, out var rval))
{
width = rval;
}
if (int.TryParse(resolutionParts[1], NumberStyles.Integer, UsCulture, out rval))
{
height = rval;
}
}
break;
}
case "BannerPath":
{
url = reader.ReadElementContentAsString() ?? string.Empty;
break;
}
case "Season":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
bannerSeason = int.Parse(val);
}
break;
}
default:
reader.Skip();
break;
}
}
else
{
reader.Read();
}
}
if (!string.IsNullOrEmpty(url) && bannerSeason.HasValue && bannerSeason.Value == seasonNumber)
{
var imageInfo = new RemoteImageInfo
{
RatingType = RatingType.Score,
CommunityRating = rating,
VoteCount = voteCount,
Url = TVUtils.BannerUrl + url,
ProviderName = ProviderName,
Language = language,
Width = width,
Height = height
};
if (!string.IsNullOrEmpty(thumbnailUrl))
{
imageInfo.ThumbnailUrl = TVUtils.BannerUrl + thumbnailUrl;
}
if (string.Equals(bannerType, "season", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(bannerType2, "season", StringComparison.OrdinalIgnoreCase))
{
imageInfo.Type = ImageType.Primary;
images.Add(imageInfo);
}
else if (string.Equals(bannerType2, "seasonwide", 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 => 0; public int Order => 0;
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)

View File

@ -1,40 +1,32 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Xml; using TvDbSharper;
using TvDbSharper.Dto;
using RatingType = MediaBrowser.Model.Dto.RatingType;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace MediaBrowser.Providers.TV.TheTVDB namespace MediaBrowser.Providers.TV.TheTVDB
{ {
public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{ {
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IFileSystem _fileSystem; private readonly TvDbClient _tvDbClient = new TvDbClient();
private readonly IXmlReaderSettingsFactory _xmlReaderSettingsFactory;
public TvdbSeriesImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) public TvdbSeriesImageProvider(IHttpClient httpClient)
{ {
_config = config;
_httpClient = httpClient; _httpClient = httpClient;
_fileSystem = fileSystem; _tvDbClient.Authentication.AuthenticateAsync(TVUtils.TvdbApiKey);
_xmlReaderSettingsFactory = xmlReaderSettingsFactory;
} }
public string Name => ProviderName; public string Name => ProviderName;
@ -58,92 +50,66 @@ namespace MediaBrowser.Providers.TV.TheTVDB
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{ {
if (TvdbSeriesProvider.IsValidSeries(item.ProviderIds)) if (!TvdbSeriesProvider.IsValidSeries(item.ProviderIds))
{ {
return new RemoteImageInfo[] { };
}
var language = item.GetPreferredMetadataLanguage(); var language = item.GetPreferredMetadataLanguage();
_tvDbClient.AcceptedLanguage = language;
var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, item.Name, item.ProductionYear, language, cancellationToken).ConfigureAwait(false); var remoteImages = new List<RemoteImageInfo>();
var keyTypes = new[] {KeyType.Poster, KeyType.Series, KeyType.Fanart};
if (string.IsNullOrEmpty(seriesDataPath)) // TODO error handling
foreach (KeyType keyType in keyTypes)
{ {
return new RemoteImageInfo[] { }; var imageQuery = new ImagesQuery
}
var path = Path.Combine(seriesDataPath, "banners.xml");
try
{ {
return GetImages(path, language, cancellationToken); KeyType = keyType
};
var imageResults =
await _tvDbClient.Series.GetImagesAsync(Convert.ToInt32(item.GetProviderId(MetadataProviders.Tvdb)), imageQuery, cancellationToken);
remoteImages.AddRange(GetImages(imageResults.Data, language));
} }
catch (FileNotFoundException) return remoteImages;
}
private IEnumerable<RemoteImageInfo> GetImages(Image[] images, string preferredLanguage)
{ {
// No tvdb data yet. Don't blow up
}
catch (IOException)
{
// No tvdb data yet. Don't blow up
}
}
return new RemoteImageInfo[] { };
}
private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, CancellationToken cancellationToken)
{
var settings = _xmlReaderSettingsFactory.Create(false);
settings.CheckCharacters = false;
settings.IgnoreProcessingInstructions = true;
settings.IgnoreComments = true;
var list = new List<RemoteImageInfo>(); var list = new List<RemoteImageInfo>();
using (var fileStream = _fileSystem.GetFileStream(xmlPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read)) foreach (Image image in images)
{ {
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8)) var resolution = image.Resolution.Split('x');
var imageInfo = new RemoteImageInfo
{ {
// Use XmlReader for best performance RatingType = RatingType.Score,
using (var reader = XmlReader.Create(streamReader, settings)) CommunityRating = (double?)image.RatingsInfo.Average,
{ VoteCount = image.RatingsInfo.Count,
reader.MoveToContent(); Url = TVUtils.BannerUrl + image.FileName,
reader.Read(); ProviderName = Name,
// TODO Language = image.LanguageId,
Width = Convert.ToInt32(resolution[0]),
Height = Convert.ToInt32(resolution[1]),
ThumbnailUrl = TVUtils.BannerUrl + image.Thumbnail
};
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
cancellationToken.ThrowIfCancellationRequested();
if (reader.NodeType == XmlNodeType.Element) if (string.Equals(image.KeyType, "poster", StringComparison.OrdinalIgnoreCase))
{ {
switch (reader.Name) imageInfo.Type = ImageType.Primary;
}
else if (string.Equals(image.KeyType, "series", StringComparison.OrdinalIgnoreCase))
{ {
case "Banner": imageInfo.Type = ImageType.Banner;
}
else if (string.Equals(image.KeyType, "fanart", StringComparison.OrdinalIgnoreCase))
{ {
if (reader.IsEmptyElement) imageInfo.Type = ImageType.Backdrop;
{
reader.Read();
continue;
}
using (var subtree = reader.ReadSubtree())
{
AddImage(subtree, list);
}
break;
}
default:
reader.Skip();
break;
}
}
else
{
reader.Read();
}
}
}
}
} }
list.Add(imageInfo);
}
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i => return list.OrderByDescending(i =>
@ -152,6 +118,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
{ {
return 3; return 3;
} }
if (!isLanguageEn) if (!isLanguageEn)
{ {
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
@ -159,174 +126,18 @@ namespace MediaBrowser.Providers.TV.TheTVDB
return 2; return 2;
} }
} }
if (string.IsNullOrEmpty(i.Language)) if (string.IsNullOrEmpty(i.Language))
{ {
return isLanguageEn ? 3 : 2; return isLanguageEn ? 3 : 2;
} }
return 0; return 0;
}) })
.ThenByDescending(i => i.CommunityRating ?? 0) .ThenByDescending(i => i.CommunityRating ?? 0)
.ThenByDescending(i => i.VoteCount ?? 0); .ThenByDescending(i => i.VoteCount ?? 0);
} }
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;
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "Rating":
{
var val = reader.ReadElementContentAsString() ?? string.Empty;
if (double.TryParse(val, NumberStyles.Any, _usCulture, out var rval))
{
rating = rval;
}
break;
}
case "RatingCount":
{
var val = reader.ReadElementContentAsString() ?? string.Empty;
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var 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)
{
if (int.TryParse(resolutionParts[0], NumberStyles.Integer, _usCulture, out var 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;
}
}
else
{
reader.Read();
}
}
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 => 0; public int Order => 0;
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)

View File

@ -30,7 +30,6 @@ namespace MediaBrowser.Providers.TV.TheTVDB
public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
{ {
internal static TvdbSeriesProvider Current { get; private set; } internal static TvdbSeriesProvider Current { get; private set; }
private readonly IZipClient _zipClient;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IXmlReaderSettingsFactory _xmlSettings; private readonly IXmlReaderSettingsFactory _xmlSettings;
@ -41,9 +40,8 @@ namespace MediaBrowser.Providers.TV.TheTVDB
private readonly ILocalizationManager _localizationManager; private readonly ILocalizationManager _localizationManager;
private readonly TvDbClient _tvDbClient; private readonly TvDbClient _tvDbClient;
public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IXmlReaderSettingsFactory xmlSettings, ILocalizationManager localizationManager) public TvdbSeriesProvider(IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IXmlReaderSettingsFactory xmlSettings, ILocalizationManager localizationManager)
{ {
_zipClient = zipClient;
_httpClient = httpClient; _httpClient = httpClient;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_config = config; _config = config;
@ -53,6 +51,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
_localizationManager = localizationManager; _localizationManager = localizationManager;
Current = this; Current = this;
_tvDbClient = new TvDbClient(); _tvDbClient = new TvDbClient();
_tvDbClient.Authentication.AuthenticateAsync(TVUtils.TvdbApiKey);
} }
private string NormalizeLanguage(string language) private string NormalizeLanguage(string language)
@ -93,8 +92,10 @@ namespace MediaBrowser.Providers.TV.TheTVDB
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo itemId, CancellationToken cancellationToken) public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo itemId, CancellationToken cancellationToken)
{ {
var result = new MetadataResult<Series>(); var result = new MetadataResult<Series>
result.QueriedById = true; {
QueriedById = true
};
if (!IsValidSeries(itemId.ProviderIds)) if (!IsValidSeries(itemId.ProviderIds))
{ {
@ -106,13 +107,6 @@ namespace MediaBrowser.Providers.TV.TheTVDB
if (IsValidSeries(itemId.ProviderIds)) if (IsValidSeries(itemId.ProviderIds))
{ {
var seriesDataPath = await EnsureSeriesInfo(itemId.ProviderIds, itemId.Name, itemId.Year, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(seriesDataPath))
{
return result;
}
result.Item = new Series(); result.Item = new Series();
result.HasMetadata = true; result.HasMetadata = true;
@ -122,156 +116,43 @@ namespace MediaBrowser.Providers.TV.TheTVDB
return result; return result;
} }
/// <summary> private async Task FetchSeriesData(MetadataResult<Series> result, string metadataLanguage, Dictionary<string, string> seriesProviderIds, CancellationToken cancellationToken)
/// Fetches the series data.
/// </summary>
/// <param name="result">The result.</param>
/// <param name="metadataLanguage">The metadata language.</param>
/// <param name="seriesProviderIds">The series provider ids.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
private void FetchSeriesData(MetadataResult<Series> result, string metadataLanguage, Dictionary<string, string> seriesProviderIds, CancellationToken cancellationToken)
{ {
_tvDbClient.AcceptedLanguage = NormalizeLanguage(metadataLanguage); _tvDbClient.AcceptedLanguage = NormalizeLanguage(metadataLanguage);
var series = result.Item; var series = result.Item;
TvDbResponse<SeriesSearchResult[]> searchResult;
if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out var tvdbId) && !string.IsNullOrEmpty(id)) if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out var tvdbId) && !string.IsNullOrEmpty(tvdbId))
{ {
series.SetProviderId(MetadataProviders.Tvdb, tvdbId); series.SetProviderId(MetadataProviders.Tvdb, tvdbId);
} }
if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(id)) if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(imdbId))
{ {
series.SetProviderId(MetadataProviders.Imdb, imdbId); series.SetProviderId(MetadataProviders.Imdb, imdbId);
tvdbId = GetSeriesByRemoteId(imdbId, MetadataProviders.Imdb.ToString(), metadataLanguage, cancellationToken).Result.ToString(); tvdbId = await GetSeriesByRemoteId(imdbId, MetadataProviders.Imdb.ToString(), metadataLanguage, cancellationToken);
} }
if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out var zap2It) && !string.IsNullOrEmpty(id)) if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out var zap2It) && !string.IsNullOrEmpty(zap2It))
{ {
series.SetProviderId(MetadataProviders.Zap2It, zap2It); series.SetProviderId(MetadataProviders.Zap2It, zap2It);
tvdbId = GetSeriesByRemoteId(zap2It, MetadataProviders.Zap2It.ToString(), metadataLanguage, cancellationToken).Result.ToString(); tvdbId = await GetSeriesByRemoteId(zap2It, MetadataProviders.Zap2It.ToString(), metadataLanguage, cancellationToken);
} }
var seriesResult = _tvDbClient.Series.GetAsync(Convert.ToInt32(tvdbId), cancellationToken).Result; // TODO call this function elsewhere?
// var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds); var seriesResult = await _tvDbClient.Series.GetAsync(Convert.ToInt32(tvdbId), cancellationToken);
//
// var seriesXmlPath = GetSeriesXmlPath(seriesProviderIds, metadataLanguage);
// var actorsXmlPath = Path.Combine(seriesDataPath, "actors.xml");
FetchSeriesInfo(result, searchResult, cancellationToken); // TODO error handling
MapSeriesToResult(result, seriesResult.Data, cancellationToken);
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
result.ResetPeople(); result.ResetPeople();
FetchActors(result, actorsXmlPath); var actorsResult = await _tvDbClient.Series.GetActorsAsync(Convert.ToInt32(tvdbId), cancellationToken);
MapActorsToResult(result, actorsResult.Data);
} }
/// <summary> private async Task<string> GetSeriesByRemoteId(string id, string idType, string language, CancellationToken cancellationToken)
/// Downloads the series zip.
/// </summary>
internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
try
{
await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
return;
}
catch (HttpException ex)
{
if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
{
throw;
}
}
if (!string.Equals(preferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase))
{
await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, "en", preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
}
}
private async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, string saveAsMetadataLanguage, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(seriesId))
{
throw new ArgumentNullException(nameof(seriesId));
}
if (!string.Equals(idType, "tvdb", StringComparison.OrdinalIgnoreCase))
{
seriesId = (await GetSeriesByRemoteId(seriesId, idType, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false)).ToString();
}
// If searching by remote id came up empty, then do a regular search
if (string.IsNullOrWhiteSpace(seriesId) && !string.IsNullOrWhiteSpace(seriesName))
{
var searchInfo = new SeriesInfo
{
Name = seriesName,
Year = seriesYear,
MetadataLanguage = preferredMetadataLanguage
};
var results = await GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
var result = results.FirstOrDefault();
if (result != null)
{
seriesId = result.GetProviderId(MetadataProviders.Tvdb);
}
}
if (string.IsNullOrWhiteSpace(seriesId))
{
throw new ArgumentNullException(nameof(seriesId));
}
var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, NormalizeLanguage(preferredMetadataLanguage));
using (var response = await _httpClient.SendAsync(new HttpRequestOptions
{
Url = url,
CancellationToken = cancellationToken,
BufferContent = false
}, "GET").ConfigureAwait(false))
{
using (var zipStream = response.Content)
{
// Delete existing files
DeleteXmlFiles(seriesDataPath);
// Copy to memory stream because we need a seekable stream
using (var ms = new MemoryStream())
{
await zipStream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
_zipClient.ExtractAllFromZip(ms, seriesDataPath, true);
}
}
}
// Sanitize all files, except for extracted episode files
foreach (var file in _fileSystem.GetFilePaths(seriesDataPath, true).ToList()
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
.Where(i => !Path.GetFileName(i).StartsWith("episode-", StringComparison.OrdinalIgnoreCase)))
{
await SanitizeXmlFile(file).ConfigureAwait(false);
}
var downloadLangaugeXmlFile = Path.Combine(seriesDataPath, NormalizeLanguage(preferredMetadataLanguage) + ".xml");
var saveAsLanguageXmlFile = Path.Combine(seriesDataPath, saveAsMetadataLanguage + ".xml");
if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase))
{
File.Copy(downloadLangaugeXmlFile, saveAsLanguageXmlFile, true);
}
await ExtractEpisodes(seriesDataPath, downloadLangaugeXmlFile, lastTvDbUpdateTime).ConfigureAwait(false);
}
private async Task<int> GetSeriesByRemoteId(string id, string idType, string language, CancellationToken cancellationToken)
{ {
_tvDbClient.AcceptedLanguage = NormalizeLanguage(language); _tvDbClient.AcceptedLanguage = NormalizeLanguage(language);
TvDbResponse<SeriesSearchResult[]> result; TvDbResponse<SeriesSearchResult[]> result;
@ -285,7 +166,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
result = await _tvDbClient.Search.SearchSeriesByImdbIdAsync(id, cancellationToken); result = await _tvDbClient.Search.SearchSeriesByImdbIdAsync(id, cancellationToken);
} }
return result.Data.First().Id; return result.Data.First().Id.ToString();
} }
internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds) internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds)
@ -395,10 +276,10 @@ namespace MediaBrowser.Providers.TV.TheTVDB
Name = tvdbTitles.FirstOrDefault(), Name = tvdbTitles.FirstOrDefault(),
ProductionYear = firstAired.Year, ProductionYear = firstAired.Year,
SearchProviderName = Name, SearchProviderName = Name,
ImageUrl = seriesSearchResult.Banner ImageUrl = TVUtils.BannerUrl + seriesSearchResult.Banner
}; };
// TODO requires another query // TODO requires another query, is it worth it?
// remoteSearchResult.SetProviderId(MetadataProviders.Imdb, seriesSearchResult.Id); // remoteSearchResult.SetProviderId(MetadataProviders.Imdb, seriesSearchResult.Id);
remoteSearchResult.SetProviderId(MetadataProviders.Tvdb, seriesSearchResult.Id.ToString()); remoteSearchResult.SetProviderId(MetadataProviders.Tvdb, seriesSearchResult.Id.ToString());
list.Add(new Tuple<List<string>, RemoteSearchResult>(tvdbTitles, remoteSearchResult)); list.Add(new Tuple<List<string>, RemoteSearchResult>(tvdbTitles, remoteSearchResult));
@ -468,27 +349,26 @@ namespace MediaBrowser.Providers.TV.TheTVDB
return name.Trim(); return name.Trim();
} }
private void FetchSeriesInfo(MetadataResult<Series> result, TvDbResponse<TvDbSharper.Dto.Series> seriesResponse, CancellationToken cancellationToken) private static void MapSeriesToResult(MetadataResult<Series> result, TvDbSharper.Dto.Series tvdbSeries, CancellationToken cancellationToken)
{ {
var episodeAirDates = new List<DateTime>(); var episodeAirDates = new List<DateTime>();
var series = result.Item; Series series = result.Item;
Series item = result.Item; series.SetProviderId(MetadataProviders.Tvdb, tvdbSeries.Id.ToString());
series.SetProviderId(MetadataProviders.Tvdb, seriesResponse.Data.Id.ToString()); series.Name = tvdbSeries.SeriesName;
series.Name = seriesResponse.Data.SeriesName; series.Overview = (tvdbSeries.Overview ?? string.Empty).Trim();
series.Overview = (seriesResponse.Data.Overview ?? string.Empty).Trim();
// TODO result.ResultLanguage = (seriesResponse.Data. ?? string.Empty).Trim(); // TODO result.ResultLanguage = (seriesResponse.Data. ?? string.Empty).Trim();
series.AirDays = TVUtils.GetAirDays(seriesResponse.Data.AirsDayOfWeek); series.AirDays = TVUtils.GetAirDays(tvdbSeries.AirsDayOfWeek);
series.AirTime = seriesResponse.Data.AirsTime; series.AirTime = tvdbSeries.AirsTime;
series.CommunityRating = (float?)seriesResponse.Data.SiteRating; series.CommunityRating = (float?)tvdbSeries.SiteRating;
series.SetProviderId(MetadataProviders.Imdb, seriesResponse.Data.ImdbId); series.SetProviderId(MetadataProviders.Imdb, tvdbSeries.ImdbId);
series.SetProviderId(MetadataProviders.Zap2It, seriesResponse.Data.Zap2itId); series.SetProviderId(MetadataProviders.Zap2It, tvdbSeries.Zap2itId);
if (Enum.TryParse(seriesResponse.Data.Status, true, out SeriesStatus seriesStatus)) if (Enum.TryParse(tvdbSeries.Status, true, out SeriesStatus seriesStatus))
{ {
series.Status = seriesStatus; series.Status = seriesStatus;
} }
if (DateTime.TryParse(seriesResponse.Data.FirstAired, out var date)) if (DateTime.TryParse(tvdbSeries.FirstAired, out var date))
{ {
date = date.ToUniversalTime(); date = date.ToUniversalTime();
@ -496,697 +376,40 @@ namespace MediaBrowser.Providers.TV.TheTVDB
series.ProductionYear = date.Year; series.ProductionYear = date.Year;
} }
series.RunTimeTicks = TimeSpan.FromMinutes(Convert.ToDouble(seriesResponse.Data.Runtime)).Ticks; series.RunTimeTicks = TimeSpan.FromMinutes(Convert.ToDouble(tvdbSeries.Runtime)).Ticks;
foreach (var genre in tvdbSeries.Genre)
if (!string.IsNullOrWhiteSpace(val))
{ {
var vals = val series.AddGenre(genre);
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.Select(i => i.Trim())
.Where(i => !string.IsNullOrWhiteSpace(i))
.ToList();
if (vals.Count > 0)
{
item.Genres = Array.Empty<string>();
foreach (var genre in vals)
{
item.AddGenre(genre);
}
}
} }
var val = reader.ReadElementContentAsString(); // TODO is network == studio?
series.AddStudio(tvdbSeries.Network);
if (!string.IsNullOrWhiteSpace(val))
{
var vals = val
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.Select(i => i.Trim())
.Where(i => !string.IsNullOrWhiteSpace(i))
.ToList();
if (vals.Count > 0)
{
item.SetStudios(vals);
}
}
// using (var fileStream = _fileSystem.GetFileStream(seriesXmlPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
// {
// using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
// {
// // Use XmlReader for best performance
// using (var reader = XmlReader.Create(streamReader, settings))
// {
// reader.MoveToContent();
// reader.Read();
//
// // Loop through each element
// while (!reader.EOF && reader.ReadState == ReadState.Interactive)
// {
// cancellationToken.ThrowIfCancellationRequested();
//
// if (reader.NodeType == XmlNodeType.Element)
// {
// switch (reader.Name)
// {
// case "Series":
// {
// if (reader.IsEmptyElement)
// {
// reader.Read();
// continue;
// }
// using (var subtree = reader.ReadSubtree())
// {
// FetchDataFromSeriesNode(result, subtree, cancellationToken);
// }
// break;
// }
//
// case "Episode":
// {
// if (reader.IsEmptyElement)
// {
// reader.Read();
// continue;
// }
// using (var subtree = reader.ReadSubtree())
// {
// var date = GetFirstAiredDateFromEpisodeNode(subtree, cancellationToken);
//
// if (date.HasValue)
// {
// episiodeAirDates.Add(date.Value);
// }
// }
// break;
// }
//
// default:
// reader.Skip();
// break;
// }
// }
// else
// {
// reader.Read();
// }
// }
// }
// }
// }
// TODO is this necessary?
if (result.Item.Status.HasValue && result.Item.Status.Value == SeriesStatus.Ended && episodeAirDates.Count > 0) if (result.Item.Status.HasValue && result.Item.Status.Value == SeriesStatus.Ended && episodeAirDates.Count > 0)
{ {
result.Item.EndDate = episodeAirDates.Max(); result.Item.EndDate = episodeAirDates.Max();
} }
} }
private DateTime? GetFirstAiredDateFromEpisodeNode(XmlReader reader, CancellationToken cancellationToken) private static void MapActorsToResult(MetadataResult<Series> result, IEnumerable<Actor> actors)
{ {
DateTime? airDate = null; foreach (Actor actor in actors)
int? seasonNumber = null;
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{ {
cancellationToken.ThrowIfCancellationRequested(); var personInfo = new PersonInfo
if (reader.NodeType == XmlNodeType.Element)
{ {
switch (reader.Name) Type = PersonType.Actor,
{ Name = (actor.Name ?? string.Empty).Trim(),
case "FirstAired": Role = actor.Role,
{ ImageUrl = actor.Image,
var val = reader.ReadElementContentAsString(); SortOrder = actor.SortOrder
};
if (!string.IsNullOrWhiteSpace(val))
{
if (DateTime.TryParse(val, out var date))
{
airDate = date.ToUniversalTime();
}
}
break;
}
case "SeasonNumber":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
// int.TryParse is local aware, so it can be probamatic, force us culture
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
{
seasonNumber = rval;
}
}
break;
}
default:
reader.Skip();
break;
}
}
else
{
reader.Read();
}
}
if (seasonNumber.HasValue && seasonNumber.Value != 0)
{
return airDate;
}
return null;
}
/// <summary>
/// Fetches the actors.
/// </summary>
/// <param name="result">The result.</param>
/// <param name="actorsXmlPath">The actors XML path.</param>
private void FetchActors(MetadataResult<Series> result, string actorsXmlPath)
{
var settings = _xmlSettings.Create(false);
settings.CheckCharacters = false;
settings.IgnoreProcessingInstructions = true;
settings.IgnoreComments = true;
using (var fileStream = _fileSystem.GetFileStream(actorsXmlPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
{
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
{
// Use XmlReader for best performance
using (var reader = XmlReader.Create(streamReader, settings))
{
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "Actor":
{
if (reader.IsEmptyElement)
{
reader.Read();
continue;
}
using (var subtree = reader.ReadSubtree())
{
FetchDataFromActorNode(result, subtree);
}
break;
}
default:
reader.Skip();
break;
}
}
else
{
reader.Read();
}
}
}
}
}
}
/// <summary>
/// Fetches the data from actor node.
/// </summary>
/// <param name="result">The result.</param>
/// <param name="reader">The reader.</param>
private void FetchDataFromActorNode(MetadataResult<Series> result, XmlReader reader)
{
reader.MoveToContent();
var personInfo = new PersonInfo();
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "Name":
{
personInfo.Name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
break;
}
case "Role":
{
personInfo.Role = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
break;
}
case "id":
{
reader.Skip();
break;
}
case "Image":
{
var url = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
if (!string.IsNullOrWhiteSpace(url))
{
personInfo.ImageUrl = TVUtils.BannerUrl + url;
}
break;
}
case "SortOrder":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
// int.TryParse is local aware, so it can be probamatic, force us culture
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var rval))
{
personInfo.SortOrder = rval;
}
}
break;
}
default:
reader.Skip();
break;
}
}
else
{
reader.Read();
}
}
personInfo.Type = PersonType.Actor;
if (!string.IsNullOrWhiteSpace(personInfo.Name)) if (!string.IsNullOrWhiteSpace(personInfo.Name))
{ {
result.AddPerson(personInfo); result.AddPerson(personInfo);
} }
} }
private void FetchDataFromSeriesNode(MetadataResult<Series> result, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
}
/// <summary>
/// Extracts info for each episode into invididual xml files so that they can be easily accessed without having to step through the entire series xml
/// </summary>
/// <param name="seriesDataPath">The series data path.</param>
/// <param name="xmlFile">The XML file.</param>
/// <param name="lastTvDbUpdateTime">The last tv db update time.</param>
/// <returns>Task.</returns>
private async Task ExtractEpisodes(string seriesDataPath, string xmlFile, long? lastTvDbUpdateTime)
{
var settings = _xmlSettings.Create(false);
settings.CheckCharacters = false;
settings.IgnoreProcessingInstructions = true;
settings.IgnoreComments = true;
using (var fileStream = _fileSystem.GetFileStream(xmlFile, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
{
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
{
// Use XmlReader for best performance
using (var reader = XmlReader.Create(streamReader, settings))
{
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "Episode":
{
var outerXml = reader.ReadOuterXml();
await SaveEpsiodeXml(seriesDataPath, outerXml, lastTvDbUpdateTime).ConfigureAwait(false);
break;
}
default:
reader.Skip();
break;
}
}
else
{
reader.Read();
}
}
}
}
}
}
private async Task SaveEpsiodeXml(string seriesDataPath, string xml, long? lastTvDbUpdateTime)
{
var settings = _xmlSettings.Create(false);
settings.CheckCharacters = false;
settings.IgnoreProcessingInstructions = true;
settings.IgnoreComments = true;
var seasonNumber = -1;
var episodeNumber = -1;
var absoluteNumber = -1;
var lastUpdateString = string.Empty;
var dvdSeasonNumber = -1;
var dvdEpisodeNumber = -1.0;
using (var streamReader = new StringReader(xml))
{
// Use XmlReader for best performance
using (var reader = XmlReader.Create(streamReader, settings))
{
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "lastupdated":
{
lastUpdateString = reader.ReadElementContentAsString();
break;
}
case "EpisodeNumber":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var num))
{
episodeNumber = num;
}
}
break;
}
case "Combined_episodenumber":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
if (float.TryParse(val, NumberStyles.Any, _usCulture, out var num))
{
dvdEpisodeNumber = num;
}
}
break;
}
case "Combined_season":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
if (float.TryParse(val, NumberStyles.Any, _usCulture, out var num))
{
dvdSeasonNumber = Convert.ToInt32(num);
}
}
break;
}
case "absolute_number":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var num))
{
absoluteNumber = num;
}
}
break;
}
case "SeasonNumber":
{
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var num))
{
seasonNumber = num;
}
}
break;
}
default:
reader.Skip();
break;
}
}
else
{
reader.Read();
}
}
}
}
var hasEpisodeChanged = true;
if (!string.IsNullOrWhiteSpace(lastUpdateString) && lastTvDbUpdateTime.HasValue)
{
if (long.TryParse(lastUpdateString, NumberStyles.Any, _usCulture, out var num))
{
hasEpisodeChanged = num >= lastTvDbUpdateTime.Value;
}
}
var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber, episodeNumber));
// Only save the file if not already there, or if the episode has changed
if (hasEpisodeChanged || !File.Exists(file))
{
using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
{
using (var writer = XmlWriter.Create(fileStream, new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Async = true
}))
{
await writer.WriteRawAsync(xml).ConfigureAwait(false);
}
}
}
if (absoluteNumber != -1)
{
file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", absoluteNumber));
// Only save the file if not already there, or if the episode has changed
if (hasEpisodeChanged || !File.Exists(file))
{
using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
{
using (var writer = XmlWriter.Create(fileStream, new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Async = true
}))
{
await writer.WriteRawAsync(xml).ConfigureAwait(false);
}
}
}
}
if (dvdSeasonNumber != -1 && dvdEpisodeNumber != -1 && (dvdSeasonNumber != seasonNumber || dvdEpisodeNumber != episodeNumber))
{
file = Path.Combine(seriesDataPath, string.Format("episode-dvd-{0}-{1}.xml", dvdSeasonNumber, dvdEpisodeNumber));
// Only save the file if not already there, or if the episode has changed
if (hasEpisodeChanged || !File.Exists(file))
{
using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
{
using (var writer = XmlWriter.Create(fileStream, new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Async = true
}))
{
await writer.WriteRawAsync(xml).ConfigureAwait(false);
}
}
}
}
}
/// <summary>
/// Gets the series data path.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <param name="seriesProviderIds">The series provider ids.</param>
/// <returns>System.String.</returns>
internal static string GetSeriesDataPath(IApplicationPaths appPaths, Dictionary<string, string> seriesProviderIds)
{
if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out string seriesId) && !string.IsNullOrEmpty(seriesId))
{
var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
return seriesDataPath;
}
if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out seriesId) && !string.IsNullOrEmpty(seriesId))
{
var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
return seriesDataPath;
}
if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out seriesId) && !string.IsNullOrEmpty(seriesId))
{
var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
return seriesDataPath;
}
return null;
}
public string GetSeriesXmlPath(Dictionary<string, string> seriesProviderIds, string language)
{
var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);
var seriesXmlFilename = language.ToLowerInvariant() + ".xml";
return Path.Combine(seriesDataPath, seriesXmlFilename);
}
/// <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.CachePath, "tvdb");
return dataPath;
}
private void DeleteXmlFiles(string path)
{
try
{
foreach (var file in _fileSystem.GetFilePaths(path, true)
.ToList())
{
_fileSystem.DeleteFile(file);
}
}
catch (IOException)
{
// No biggie
}
}
/// <summary>
/// Sanitizes the XML file.
/// </summary>
/// <param name="file">The file.</param>
/// <returns>Task.</returns>
private async Task SanitizeXmlFile(string file)
{
string validXml;
using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
{
using (var reader = new StreamReader(fileStream))
{
var xml = await reader.ReadToEndAsync().ConfigureAwait(false);
validXml = StripInvalidXmlCharacters(xml);
}
}
using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
{
using (var writer = new StreamWriter(fileStream))
{
await writer.WriteAsync(validXml).ConfigureAwait(false);
}
}
}
/// <summary>
/// Strips the invalid XML characters.
/// </summary>
/// <param name="inString">The in string.</param>
/// <returns>System.String.</returns>
public static string StripInvalidXmlCharacters(string inString)
{
if (inString == null) return null;
var sbOutput = new StringBuilder();
char ch;
for (int i = 0; i < inString.Length; i++)
{
ch = inString[i];
if ((ch >= 0x0020 && ch <= 0xD7FF) ||
(ch >= 0xE000 && ch <= 0xFFFD) ||
ch == 0x0009 ||
ch == 0x000A ||
ch == 0x000D)
{
sbOutput.Append(ch);
}
}
return sbOutput.ToString();
} }
public string Name => "TheTVDB"; public string Name => "TheTVDB";