diff options
| author | LukePulverenti <luke.pulverenti@gmail.com> | 2013-02-20 20:33:05 -0500 |
|---|---|---|
| committer | LukePulverenti <luke.pulverenti@gmail.com> | 2013-02-20 20:33:05 -0500 |
| commit | 767cdc1f6f6a63ce997fc9476911e2c361f9d402 (patch) | |
| tree | 49add55976f895441167c66cfa95e5c7688d18ce /MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs | |
| parent | 845554722efaed872948a9e0f7202e3ef52f1b6e (diff) | |
Pushing missing changes
Diffstat (limited to 'MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs')
| -rw-r--r-- | MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs b/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs new file mode 100644 index 000000000..901d39040 --- /dev/null +++ b/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs @@ -0,0 +1,545 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Resolvers.TV; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Net; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; + +namespace MediaBrowser.Controller.Providers.TV +{ + /// <summary> + /// Class RemoteSeriesProvider + /// </summary> + [Export(typeof(BaseMetadataProvider))] + class RemoteSeriesProvider : BaseMetadataProvider + { + + /// <summary> + /// The root URL + /// </summary> + private const string rootUrl = "http://www.thetvdb.com/api/"; + /// <summary> + /// The series query + /// </summary> + private const string seriesQuery = "GetSeries.php?seriesname={0}"; + /// <summary> + /// The series get + /// </summary> + private const string seriesGet = "http://www.thetvdb.com/api/{0}/series/{1}/{2}.xml"; + /// <summary> + /// The get actors + /// </summary> + private const string getActors = "http://www.thetvdb.com/api/{0}/series/{1}/actors.xml"; + + /// <summary> + /// The LOCA l_ MET a_ FIL e_ NAME + /// </summary> + protected const string LOCAL_META_FILE_NAME = "Series.xml"; + + /// <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; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Second; } + } + + /// <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 true; + } + } + + /// <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) + { + var downloadDate = providerInfo.LastRefreshed; + + if (Kernel.Instance.Configuration.MetadataRefreshDays == -1 && downloadDate != DateTime.MinValue) + { + return false; + } + + if (item.DontFetchMeta) return false; + + return !HasLocalMeta(item) && (Kernel.Instance.Configuration.MetadataRefreshDays != -1 && + DateTime.UtcNow.Subtract(downloadDate).TotalDays > Kernel.Instance.Configuration.MetadataRefreshDays); + } + + /// <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> + protected override async Task<bool> FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var series = (Series)item; + if (!item.DontFetchMeta && !HasLocalMeta(series)) + { + var path = item.Path ?? ""; + var seriesId = Path.GetFileName(path).GetAttributeValue("tvdbid") ?? await GetSeriesId(series, cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + + if (!string.IsNullOrEmpty(seriesId)) + { + series.SetProviderId(MetadataProviders.Tvdb, seriesId); + if (!HasCompleteMetadata(series)) + { + await FetchSeriesData(series, seriesId, cancellationToken).ConfigureAwait(false); + } + } + SetLastRefreshed(item, DateTime.UtcNow); + return true; + } + Logger.Info("Series provider not fetching because local meta exists or requested to ignore: " + item.Name); + return false; + + } + + /// <summary> + /// Fetches the series data. + /// </summary> + /// <param name="series">The series.</param> + /// <param name="seriesId">The series id.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{System.Boolean}.</returns> + private async Task<bool> FetchSeriesData(Series series, string seriesId, CancellationToken cancellationToken) + { + var success = false; + + var name = series.Name; + Logger.Debug("TvDbProvider: Fetching series data: " + name); + + if (!string.IsNullOrEmpty(seriesId)) + { + + string url = string.Format(seriesGet, TVUtils.TVDBApiKey, seriesId, Kernel.Instance.Configuration.PreferredMetadataLanguage); + var doc = new XmlDocument(); + + try + { + using (var xml = await Kernel.Instance.HttpManager.Get(url, Kernel.Instance.ResourcePools.TvDb, cancellationToken).ConfigureAwait(false)) + { + doc.Load(xml); + } + } + catch (HttpException) + { + } + + if (doc.HasChildNodes) + { + //kick off the actor and image fetch simultaneously + var actorTask = FetchActors(series, seriesId, doc, cancellationToken); + var imageTask = FetchImages(series, seriesId, cancellationToken); + + success = true; + + series.Name = doc.SafeGetString("//SeriesName"); + series.Overview = doc.SafeGetString("//Overview"); + series.CommunityRating = doc.SafeGetSingle("//Rating", 0, 10); + series.AirDays = TVUtils.GetAirDays(doc.SafeGetString("//Airs_DayOfWeek")); + series.AirTime = doc.SafeGetString("//Airs_Time"); + + string n = doc.SafeGetString("//banner"); + if (!string.IsNullOrWhiteSpace(n)) + { + series.SetImage(ImageType.Banner, TVUtils.BannerUrl + n); + } + + string s = doc.SafeGetString("//Network"); + if (!string.IsNullOrWhiteSpace(s)) + series.AddStudios(new List<string>(s.Trim().Split('|'))); + + series.OfficialRating = doc.SafeGetString("//ContentRating"); + + string g = doc.SafeGetString("//Genre"); + + if (g != null) + { + string[] genres = g.Trim('|').Split('|'); + if (g.Length > 0) + { + series.AddGenres(genres); + } + } + + //wait for other tasks + await Task.WhenAll(actorTask, imageTask).ConfigureAwait(false); + + if (Kernel.Instance.Configuration.SaveLocalMeta) + { + var ms = new MemoryStream(); + doc.Save(ms); + + await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(series, Path.Combine(series.MetaLocation, LOCAL_META_FILE_NAME), ms, cancellationToken).ConfigureAwait(false); + } + } + } + + + + return success; + } + + /// <summary> + /// Fetches the actors. + /// </summary> + /// <param name="series">The series.</param> + /// <param name="seriesId">The series id.</param> + /// <param name="doc">The doc.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task FetchActors(Series series, string seriesId, XmlDocument doc, CancellationToken cancellationToken) + { + string urlActors = string.Format(getActors, TVUtils.TVDBApiKey, seriesId); + var docActors = new XmlDocument(); + + try + { + using (var actors = await Kernel.Instance.HttpManager.Get(urlActors, Kernel.Instance.ResourcePools.TvDb, cancellationToken).ConfigureAwait(false)) + { + docActors.Load(actors); + } + } + catch (HttpException) + { + } + + if (docActors.HasChildNodes) + { + XmlNode actorsNode = null; + if (Kernel.Instance.Configuration.SaveLocalMeta) + { + //add to the main doc for saving + var seriesNode = doc.SelectSingleNode("//Series"); + if (seriesNode != null) + { + actorsNode = doc.CreateNode(XmlNodeType.Element, "Persons", null); + seriesNode.AppendChild(actorsNode); + } + } + + var xmlNodeList = docActors.SelectNodes("Actors/Actor"); + if (xmlNodeList != null) + foreach (XmlNode p in xmlNodeList) + { + string actorName = p.SafeGetString("Name"); + string actorRole = p.SafeGetString("Role"); + if (!string.IsNullOrWhiteSpace(actorName)) + { + series.AddPerson(new PersonInfo { Type = PersonType.Actor, Name = actorName, Role = actorRole }); + + if (Kernel.Instance.Configuration.SaveLocalMeta && actorsNode != null) + { + //create in main doc + var personNode = doc.CreateNode(XmlNodeType.Element, "Person", null); + foreach (XmlNode subNode in p.ChildNodes) + personNode.AppendChild(doc.ImportNode(subNode, true)); + //need to add the type + var typeNode = doc.CreateNode(XmlNodeType.Element, "Type", null); + typeNode.InnerText = "Actor"; + personNode.AppendChild(typeNode); + actorsNode.AppendChild(personNode); + } + + } + } + } + } + + /// <summary> + /// Fetches the images. + /// </summary> + /// <param name="series">The series.</param> + /// <param name="seriesId">The series id.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task FetchImages(Series series, string seriesId, CancellationToken cancellationToken) + { + if ((!string.IsNullOrEmpty(seriesId)) && ((series.PrimaryImagePath == null) || (series.BackdropImagePaths == null))) + { + string url = string.Format("http://www.thetvdb.com/api/" + TVUtils.TVDBApiKey + "/series/{0}/banners.xml", seriesId); + var images = new XmlDocument(); + + try + { + using (var imgs = await Kernel.Instance.HttpManager.Get(url, Kernel.Instance.ResourcePools.TvDb, cancellationToken).ConfigureAwait(false)) + { + images.Load(imgs); + } + } + catch (HttpException) + { + } + + if (images.HasChildNodes) + { + if (Kernel.Instance.Configuration.RefreshItemImages || !series.HasLocalImage("folder")) + { + var n = images.SelectSingleNode("//Banner[BannerType='poster']"); + if (n != null) + { + n = n.SelectSingleNode("./BannerPath"); + if (n != null) + { + try + { + series.PrimaryImagePath = await Kernel.Instance.ProviderManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), Kernel.Instance.ResourcePools.TvDb, cancellationToken).ConfigureAwait(false); + } + catch (HttpException) + { + } + catch (IOException) + { + + } + } + } + } + + if (Kernel.Instance.Configuration.DownloadTVBanner && (Kernel.Instance.Configuration.RefreshItemImages || !series.HasLocalImage("banner"))) + { + var n = images.SelectSingleNode("//Banner[BannerType='series']"); + if (n != null) + { + n = n.SelectSingleNode("./BannerPath"); + if (n != null) + { + try + { + var bannerImagePath = await Kernel.Instance.ProviderManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), Kernel.Instance.ResourcePools.TvDb, cancellationToken); + + series.SetImage(ImageType.Banner, bannerImagePath); + } + catch (HttpException) + { + } + catch (IOException) + { + + } + } + } + } + + var bdNo = 0; + var xmlNodeList = images.SelectNodes("//Banner[BannerType='fanart']"); + if (xmlNodeList != null) + foreach (XmlNode b in xmlNodeList) + { + series.BackdropImagePaths = new List<string>(); + var p = b.SelectSingleNode("./BannerPath"); + if (p != null) + { + var bdName = "backdrop" + (bdNo > 0 ? bdNo.ToString() : ""); + if (Kernel.Instance.Configuration.RefreshItemImages || !series.HasLocalImage(bdName)) + { + try + { + series.BackdropImagePaths.Add(await Kernel.Instance.ProviderManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), Kernel.Instance.ResourcePools.TvDb, cancellationToken).ConfigureAwait(false)); + } + catch (HttpException) + { + } + catch (IOException) + { + + } + } + bdNo++; + if (bdNo >= Kernel.Instance.Configuration.MaxBackdrops) break; + } + } + } + } + } + + /// <summary> + /// Determines whether [has complete metadata] [the specified series]. + /// </summary> + /// <param name="series">The series.</param> + /// <returns><c>true</c> if [has complete metadata] [the specified series]; otherwise, <c>false</c>.</returns> + private bool HasCompleteMetadata(Series series) + { + return (series.HasImage(ImageType.Banner)) && (series.CommunityRating != null) + && (series.Overview != null) && (series.Name != null) && (series.People != null) + && (series.Genres != null) && (series.OfficialRating != null); + } + + /// <summary> + /// Determines whether [has local meta] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <returns><c>true</c> if [has local meta] [the specified item]; otherwise, <c>false</c>.</returns> + private bool HasLocalMeta(BaseItem item) + { + //need at least the xml and folder.jpg/png + return item.ResolveArgs.ContainsMetaFileByName(LOCAL_META_FILE_NAME) && (item.ResolveArgs.ContainsMetaFileByName("folder.jpg") || + item.ResolveArgs.ContainsMetaFileByName("folder.png")); + } + + /// <summary> + /// Gets the series id. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{System.String}.</returns> + private async Task<string> GetSeriesId(BaseItem item, CancellationToken cancellationToken) + { + var seriesId = item.GetProviderId(MetadataProviders.Tvdb); + if (string.IsNullOrEmpty(seriesId)) + { + seriesId = await FindSeries(item.Name, cancellationToken).ConfigureAwait(false); + } + return seriesId; + } + + + /// <summary> + /// Finds the series. + /// </summary> + /// <param name="name">The name.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{System.String}.</returns> + public async Task<string> FindSeries(string name, CancellationToken cancellationToken) + { + + //nope - search for it + string url = string.Format(rootUrl + seriesQuery, WebUtility.UrlEncode(name)); + var doc = new XmlDocument(); + + try + { + using (var results = await Kernel.Instance.HttpManager.Get(url, Kernel.Instance.ResourcePools.TvDb, cancellationToken).ConfigureAwait(false)) + { + doc.Load(results); + } + } + catch (HttpException) + { + } + + if (doc.HasChildNodes) + { + XmlNodeList nodes = doc.SelectNodes("//Series"); + string comparableName = GetComparableName(name); + if (nodes != null) + foreach (XmlNode node in nodes) + { + var n = node.SelectSingleNode("./SeriesName"); + if (n != null && GetComparableName(n.InnerText) == comparableName) + { + n = node.SelectSingleNode("./seriesid"); + if (n != null) + return n.InnerText; + } + else + { + if (n != null) + Logger.Info("TVDb Provider - " + n.InnerText + " did not match " + comparableName); + } + } + } + + Logger.Info("TVDb Provider - Could not find " + name + ". Check name on Thetvdb.org."); + return null; + } + + /// <summary> + /// The remove + /// </summary> + const string remove = "\"'!`?"; + /// <summary> + /// The spacers + /// </summary> + const string spacers = "/,.:;\\(){}[]+-_=–*"; // (there are not actually two - in the they are different char codes) + + /// <summary> + /// Gets the name of the comparable. + /// </summary> + /// <param name="name">The name.</param> + /// <returns>System.String.</returns> + internal static string GetComparableName(string name) + { + name = name.ToLower(); + name = name.Normalize(NormalizationForm.FormKD); + var sb = new StringBuilder(); + foreach (var c in name) + { + if ((int)c >= 0x2B0 && (int)c <= 0x0333) + { + // skip char modifier and diacritics + } + else if (remove.IndexOf(c) > -1) + { + // skip chars we are removing + } + else if (spacers.IndexOf(c) > -1) + { + sb.Append(" "); + } + else if (c == '&') + { + sb.Append(" and "); + } + else + { + sb.Append(c); + } + } + name = sb.ToString(); + name = name.Replace(", the", ""); + name = name.Replace("the ", " "); + name = name.Replace(" the ", " "); + + string prevName; + do + { + prevName = name; + name = name.Replace(" ", " "); + } while (name.Length != prevName.Length); + + return name.Trim(); + } + + + + } +} |
