public static Episode Convert(MazeEpisode mazeEpisode) { var episode = new Episode(); episode.ProviderIds[MetadataProviders.TvMaze.ToString()] = mazeEpisode.id.ToString(); episode.Name = mazeEpisode.name; episode.IndexNumber = mazeEpisode.number; episode.ParentIndexNumber = mazeEpisode.season; if (mazeEpisode.airdate.HasValue) { episode.PremiereDate = mazeEpisode.airdate.Value; } if (mazeEpisode.runtime.HasValue) { episode.RunTimeTicks = TimeSpan.FromMinutes(mazeEpisode.runtime.Value).Ticks; } episode.Overview = StripHtml(mazeEpisode.summary); return episode; }
private int CompareEpisodeToSpecial(Episode x, Episode y) { var xSeason = x.PhysicalSeasonNumber ?? -1; var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1; if (xSeason != ySeason) { return xSeason.CompareTo(ySeason); } // Now we know they have the same season // Compare episode number // Add 1 to to non-specials to account for AirsBeforeEpisodeNumber var xEpisode = x.IndexNumber ?? -1; xEpisode++; var yEpisode = y.AirsBeforeEpisodeNumber ?? 10000; // Sometimes they'll both have a value. // For example AirsAfterSeasonNumber=1, AirsBeforeSeasonNumber=2, AirsBeforeEpisodeNumber=1 // The episode should be displayed at the end of season 1 if (y.AirsAfterSeasonNumber.HasValue && y.AirsBeforeSeasonNumber.HasValue && y.AirsBeforeSeasonNumber.Value > y.AirsAfterSeasonNumber.Value) { yEpisode = 10000; } return xEpisode.CompareTo(yEpisode); }
private int CompareEpisodeToSpecial(Episode x, Episode y) { // http://thetvdb.com/wiki/index.php?title=Special_Episodes var xSeason = x.PhysicalSeasonNumber ?? -1; var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1; if (xSeason != ySeason) { return xSeason.CompareTo(ySeason); } // Special comes after episode if (y.AirsAfterSeasonNumber.HasValue) { return -1; } var yEpisode = y.AirsBeforeEpisodeNumber; // Special comes before the season if (!yEpisode.HasValue) { return 1; } // Compare episode number var xEpisode = x.IndexNumber; if (!xEpisode.HasValue) { // Can't really compare if this happens return 0; } // Special comes before episode if (xEpisode.Value == yEpisode.Value) { return 1; } return xEpisode.Value.CompareTo(yEpisode.Value); }
private int CompareEpisodeToSpecial(Episode x, Episode y) { var xSeason = x.ParentIndexNumber ?? -1; var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1; if (xSeason != ySeason) { return xSeason.CompareTo(ySeason); } // Now we know they have the same season // Compare episode number // Add 1 to to non-specials to account for AirsBeforeEpisodeNumber var xEpisode = (x.IndexNumber ?? 0) * 1000 + 1; var yEpisode = (y.AirsBeforeEpisodeNumber ?? 0) * 1000; return xEpisode.CompareTo(yEpisode); }
private int Compare(Episode x, Episode y) { var isXSpecial = (x.PhysicalSeasonNumber ?? -1) == 0; var isYSpecial = (y.PhysicalSeasonNumber ?? -1) == 0; if (isXSpecial && isYSpecial) { return CompareSpecials(x, y); } if (!isXSpecial && !isYSpecial) { return CompareEpisodes(x, y); } if (!isXSpecial) { return CompareEpisodeToSpecial(x, y); } return CompareEpisodeToSpecial(y, x) * -1; }
private int Compare(Episode x, Episode y) { var isXSpecial = (x.ParentIndexNumber ?? -1) == 0; var isYSpecial = (y.ParentIndexNumber ?? -1) == 0; if (isXSpecial && isYSpecial) { return CompareSpecials(x, y); } if (!isXSpecial && !isYSpecial) { return CompareEpisodes(x, y); } if (!isXSpecial && isYSpecial) { return CompareEpisodeToSpecial(x, y); } return CompareEpisodeToSpecial(x, y) * -1; }
/// <summary> /// Fetches the episode data. /// </summary> /// <param name="seriesXml">The series XML.</param> /// <param name="episode">The episode.</param> /// <param name="seriesId">The series id.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> private async Task<ProviderRefreshStatus> FetchEpisodeData(XmlDocument seriesXml, Episode episode, string seriesId, CancellationToken cancellationToken) { var status = ProviderRefreshStatus.Success; if (episode.IndexNumber == null) { return status; } var seasonNumber = episode.ParentIndexNumber ?? TVUtils.GetSeasonNumberFromEpisodeFile(episode.Path); if (seasonNumber == null) { return status; } var usingAbsoluteData = false; var episodeNode = seriesXml.SelectSingleNode("//Episode[EpisodeNumber='" + episode.IndexNumber.Value + "'][SeasonNumber='" + seasonNumber.Value + "']"); if (episodeNode == null) { if (seasonNumber.Value == 1) { episodeNode = seriesXml.SelectSingleNode("//Episode[absolute_number='" + episode.IndexNumber.Value + "']"); usingAbsoluteData = true; } } // If still null, nothing we can do if (episodeNode == null) { return status; } IEnumerable<XmlDocument> extraEpisodesNode = new XmlDocument[] { }; if (episode.IndexNumberEnd.HasValue) { var seriesXDocument = XDocument.Load(new XmlNodeReader(seriesXml)); if (usingAbsoluteData) { extraEpisodesNode = seriesXDocument.Descendants("Episode") .Where( x => int.Parse(x.Element("absolute_number").Value) > episode.IndexNumber && int.Parse(x.Element("absolute_number").Value) <= episode.IndexNumberEnd.Value).OrderBy(x => x.Element("absolute_number").Value).Select(x => x.ToXmlDocument()); } else { var all = seriesXDocument.Descendants("Episode").Where(x => int.Parse(x.Element("SeasonNumber").Value) == seasonNumber.Value); var xElements = all.Where(x => int.Parse(x.Element("EpisodeNumber").Value) > episode.IndexNumber && int.Parse(x.Element("EpisodeNumber").Value) <= episode.IndexNumberEnd.Value); extraEpisodesNode = xElements.OrderBy(x => x.Element("EpisodeNumber").Value).Select(x => x.ToXmlDocument()); } } var doc = new XmlDocument(); doc.LoadXml(episodeNode.OuterXml); if (!episode.HasImage(ImageType.Primary)) { var p = doc.SafeGetString("//filename"); if (p != null) { try { var url = TVUtils.BannerUrl + p; await _providerManager.SaveImage(episode, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken) .ConfigureAwait(false); } catch (HttpException) { status = ProviderRefreshStatus.CompletedWithErrors; } } } if (!episode.LockedFields.Contains(MetadataFields.Overview)) { var extraOverview = extraEpisodesNode.Aggregate("", (current, xmlDocument) => current + ("\r\n\r\n" + xmlDocument.SafeGetString("//Overview"))); episode.Overview = doc.SafeGetString("//Overview") + extraOverview; } if (usingAbsoluteData) episode.IndexNumber = doc.SafeGetInt32("//absolute_number", -1); if (episode.IndexNumber < 0) episode.IndexNumber = doc.SafeGetInt32("//EpisodeNumber"); if (!episode.LockedFields.Contains(MetadataFields.Name)) { var extraNames = extraEpisodesNode.Aggregate("", (current, xmlDocument) => current + (", " + xmlDocument.SafeGetString("//EpisodeName"))); episode.Name = doc.SafeGetString("//EpisodeName") + extraNames; } episode.CommunityRating = doc.SafeGetSingle("//Rating", -1, 10); var firstAired = doc.SafeGetString("//FirstAired"); DateTime airDate; if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850) { episode.PremiereDate = airDate.ToUniversalTime(); episode.ProductionYear = airDate.Year; } if (!episode.LockedFields.Contains(MetadataFields.Cast)) { episode.People.Clear(); var actors = doc.SafeGetString("//GuestStars"); if (actors != null) { // Sometimes tvdb actors have leading spaces //Regex Info: //The first block are the posible delimitators (open-parentheses should be there cause if dont the next block will fail) //The second block Allow the delimitators to be part of the text if they're inside parentheses var persons = Regex.Matches(actors, @"(?<delimitators>([^|,(])|(?<ignoreinParentheses>\([^)]*\)*))+") .Cast<Match>() .Select(m => m.Value) .Where(i => !string.IsNullOrWhiteSpace(i) && !string.IsNullOrEmpty(i)); foreach (var person in persons.Select(str => { var nameGroup = str.Split(new[] { '(' }, 2, StringSplitOptions.RemoveEmptyEntries); var name = nameGroup[0].Trim(); var roles = nameGroup.Count() > 1 ? nameGroup[1].Trim() : null; if (roles != null) roles = roles.EndsWith(")") ? roles.Substring(0, roles.Length - 1) : roles; return new PersonInfo { Type = PersonType.GuestStar, Name = name, Role = roles }; })) { episode.AddPerson(person); } } foreach (var xmlDocument in extraEpisodesNode) { var extraActors = xmlDocument.SafeGetString("//GuestStars"); if (extraActors == null) continue; // Sometimes tvdb actors have leading spaces var persons = Regex.Matches(extraActors, @"(?<delimitators>([^|,(])|(?<ignoreinParentheses>\([^)]*\)*))+") .Cast<Match>() .Select(m => m.Value) .Where(i => !string.IsNullOrWhiteSpace(i) && !string.IsNullOrEmpty(i)); foreach (var person in persons.Select(str => { var nameGroup = str.Split(new[] { '(' }, 2, StringSplitOptions.RemoveEmptyEntries); var name = nameGroup[0].Trim(); var roles = nameGroup.Count() > 1 ? nameGroup[1].Trim() : null; if (roles != null) roles = roles.EndsWith(")") ? roles.Substring(0, roles.Length - 1) : roles; return new PersonInfo { Type = PersonType.GuestStar, Name = name, Role = roles }; })) { episode.AddPerson(person); } } var directors = doc.SafeGetString("//Director"); if (directors != null) { // Sometimes tvdb actors have leading spaces foreach (var person in directors.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(str => new PersonInfo { Type = PersonType.Director, Name = str.Trim() })) { episode.AddPerson(person); } } var writers = doc.SafeGetString("//Writer"); if (writers != null) { // Sometimes tvdb actors have leading spaces foreach (var person in writers.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(str => new PersonInfo { Type = PersonType.Writer, Name = str.Trim() })) { episode.AddPerson(person); } } } return status; }
/// <summary> /// Reports to trakt.tv that an episode is being watched. Or that Episode(s) have been watched. /// </summary> /// <param name="episode">The episode being watched</param> /// <param name="status">Enum indicating whether an episode is being watched or scrobbled</param> /// <param name="traktUser">The user that's watching the episode</param> /// <returns>A List of standard TraktResponse Data Contracts</returns> public async Task<List<TraktResponseDataContract>> SendEpisodeStatusUpdateAsync(Episode episode, MediaStatus status, TraktUser traktUser) { var responses = new List<TraktResponseDataContract>(); // We're Scrobbling a multi-episode file if (episode.IndexNumberEnd != null && episode.IndexNumberEnd != episode.IndexNumber && status.Equals(MediaStatus.Scrobble)) { for (var i = episode.IndexNumber; i <= episode.IndexNumberEnd; i++) { var response = await SendEpisodeStatusUpdateInternalAsync(episode, i, status, traktUser); responses.Add(response); } } // It's a single-episode file, or we're just sending the watching status of a multi-episode file. else { var response = await SendEpisodeStatusUpdateInternalAsync(episode, episode.IndexNumber, status, traktUser); responses.Add(response); } return responses; }
/// <summary> /// Validates the primary image path still exists /// </summary> /// <param name="episode">The episode.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> private void ValidateImage(Episode episode) { var path = episode.PrimaryImagePath; if (string.IsNullOrEmpty(path)) { return; } if (!File.Exists(path)) { episode.PrimaryImagePath = null; } }
/// <summary> /// Adds the episode. /// </summary> /// <param name="series">The series.</param> /// <param name="seasonNumber">The season number.</param> /// <param name="episodeNumber">The episode number.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> private async Task AddEpisode(Series series, int seasonNumber, int episodeNumber, CancellationToken cancellationToken) { var season = series.Children.OfType<Season>() .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber); if (season == null) { var provider = new DummySeasonProvider(_config, _logger, _localization, _libraryManager, _fileSystem); season = await provider.AddSeason(series, seasonNumber, cancellationToken).ConfigureAwait(false); } var name = string.Format("Episode {0}", episodeNumber.ToString(_usCulture)); var episode = new Episode { Name = name, IndexNumber = episodeNumber, ParentIndexNumber = seasonNumber, Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)) }; episode.SetParent(season); await season.AddChild(episode, cancellationToken).ConfigureAwait(false); await episode.RefreshMetadata(new MetadataRefreshOptions(_fileSystem) { }, cancellationToken).ConfigureAwait(false); }
private string GetVerboseEpisodeData(Episode episode) { string episodeString = ""; episodeString += "Episode: " + (episode.ParentIndexNumber != null ? episode.ParentIndexNumber.ToString() : "null"); episodeString += "x" + (episode.IndexNumber != null ? episode.IndexNumber.ToString() : "null"); episodeString += " '" + episode.Name + "' "; episodeString += "Series: '" + (episode.Series != null ? !String.IsNullOrWhiteSpace(episode.Series.Name) ? episode.Series.Name : "null property" : "null class"); episodeString += "'"; return episodeString; }
private async Task<ProviderRefreshStatus> FetchMainEpisodeInfo(Episode item, string xmlFile, CancellationToken cancellationToken) { var status = ProviderRefreshStatus.Success; using (var streamReader = new StreamReader(xmlFile, Encoding.UTF8)) { if (!item.LockedFields.Contains(MetadataFields.Cast)) { item.People.Clear(); } // Use XmlReader for best performance using (var reader = XmlReader.Create(streamReader, new XmlReaderSettings { CheckCharacters = false, IgnoreProcessingInstructions = true, IgnoreComments = true, ValidationType = ValidationType.None })) { reader.MoveToContent(); // Loop through each element while (reader.Read()) { cancellationToken.ThrowIfCancellationRequested(); if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "id": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { item.SetProviderId(MetadataProviders.Tvdb, val); } break; } case "IMDB_ID": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { item.SetProviderId(MetadataProviders.Imdb, val); } break; } case "airsbefore_episode": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { int rval; // int.TryParse is local aware, so it can be probamatic, force us culture if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) { item.AirsBeforeEpisodeNumber = rval; } } break; } case "airsafter_season": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { int rval; // int.TryParse is local aware, so it can be probamatic, force us culture if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) { item.AirsAfterSeasonNumber = rval; } } break; } case "airsbefore_season": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { int rval; // int.TryParse is local aware, so it can be probamatic, force us culture if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) { item.AirsBeforeSeasonNumber = rval; } } break; } case "EpisodeName": { if (!item.LockedFields.Contains(MetadataFields.Name)) { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { item.Name = val; } } break; } case "Language": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { item.Language = val; } break; } case "filename": { if (string.IsNullOrEmpty(item.PrimaryImagePath)) { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { try { var url = TVUtils.BannerUrl + val; await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); } catch (HttpException) { status = ProviderRefreshStatus.CompletedWithErrors; } } } break; } case "Overview": { if (!item.LockedFields.Contains(MetadataFields.Overview)) { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { item.Overview = val; } } break; } case "Rating": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { float rval; // float.TryParse is local aware, so it can be probamatic, force us culture if (float.TryParse(val, NumberStyles.AllowDecimalPoint, _usCulture, out rval)) { item.CommunityRating = rval; } } break; } case "RatingCount": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { int rval; // int.TryParse is local aware, so it can be probamatic, force us culture if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) { item.VoteCount = rval; } } break; } case "FirstAired": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { DateTime date; if (DateTime.TryParse(val, out date)) { date = date.ToUniversalTime(); item.PremiereDate = date; item.ProductionYear = date.Year; } } break; } case "Director": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { if (!item.LockedFields.Contains(MetadataFields.Cast)) { AddPeople(item, val, PersonType.Director); } } break; } case "GuestStars": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { if (!item.LockedFields.Contains(MetadataFields.Cast)) { AddGuestStars(item, val); } } break; } case "Writer": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { if (!item.LockedFields.Contains(MetadataFields.Cast)) { AddPeople(item, val, PersonType.Writer); } } break; } default: reader.Skip(); break; } } } } } return status; }
/// <summary> /// Fetches the episode data. /// </summary> /// <param name="episode">The episode.</param> /// <param name="seriesDataPath">The series data path.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> private async Task<ProviderRefreshStatus> FetchEpisodeData(Episode episode, string seriesDataPath, CancellationToken cancellationToken) { var status = ProviderRefreshStatus.Success; if (episode.IndexNumber == null) { return status; } var episodeNumber = episode.IndexNumber.Value; var seasonNumber = episode.ParentIndexNumber; if (seasonNumber == null) { return status; } var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); var success = false; var usingAbsoluteData = false; try { status = await FetchMainEpisodeInfo(episode, file, cancellationToken).ConfigureAwait(false); success = true; } catch (FileNotFoundException) { // Could be using absolute numbering if (seasonNumber.Value != 1) { throw; } } if (!success) { file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber)); status = await FetchMainEpisodeInfo(episode, file, cancellationToken).ConfigureAwait(false); usingAbsoluteData = true; } var end = episode.IndexNumberEnd ?? episodeNumber; episodeNumber++; while (episodeNumber <= end) { if (usingAbsoluteData) { file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber)); } else { file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); } try { FetchAdditionalPartInfo(episode, file, cancellationToken); } catch (FileNotFoundException) { break; } episodeNumber++; } return status; }
/// <summary> /// Gets the episode XML files. /// </summary> /// <param name="episode">The episode.</param> /// <param name="seriesDataPath">The series data path.</param> /// <returns>List{FileInfo}.</returns> internal List<FileInfo> GetEpisodeXmlFiles(Episode episode, string seriesDataPath) { var files = new List<FileInfo>(); if (episode.IndexNumber == null) { return files; } var episodeNumber = episode.IndexNumber.Value; var seasonNumber = episode.ParentIndexNumber; if (seasonNumber == null) { return files; } var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); var fileInfo = new FileInfo(file); var usingAbsoluteData = false; if (fileInfo.Exists) { files.Add(fileInfo); } else { file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber)); fileInfo = new FileInfo(file); if (fileInfo.Exists) { files.Add(fileInfo); usingAbsoluteData = true; } } var end = episode.IndexNumberEnd ?? episodeNumber; episodeNumber++; while (episodeNumber <= end) { if (usingAbsoluteData) { file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber)); } else { file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); } fileInfo = new FileInfo(file); if (fileInfo.Exists) { files.Add(fileInfo); } else { break; } episodeNumber++; } return files; }
private async Task<TraktResponseDataContract> SendEpisodeStatusUpdateInternalAsync(Episode episode, int? index, MediaStatus status, TraktUser traktUser) { var data = new Dictionary<string, string> { {"username", traktUser.UserName}, {"password", traktUser.PasswordHash} }; if (episode.Series.ProviderIds != null) { if (episode.Series.ProviderIds.ContainsKey("Imdb")) data.Add("imdb_id", episode.Series.ProviderIds["Imdb"]); if (episode.Series.ProviderIds.ContainsKey("Tvdb")) data.Add("tvdb_id", episode.Series.ProviderIds["Tvdb"]); } if (episode.Series == null || episode.AiredSeasonNumber == null) return null; data.Add("title", episode.Series.Name); data.Add("year", episode.Series.ProductionYear != null ? episode.Series.ProductionYear.ToString() : ""); data.Add("season", episode.AiredSeasonNumber != null ? episode.AiredSeasonNumber.ToString() : ""); data.Add("episode", index != null ? index.ToString() : ""); data.Add("duration", episode.RunTimeTicks != null ? ((int)((episode.RunTimeTicks / 10000000) / 60)).ToString(CultureInfo.InvariantCulture) : ""); Stream response = null; if (status == MediaStatus.Watching) response = await _httpClient.Post(TraktUris.ShowWatching, data, Plugin.Instance.TraktResourcePool, CancellationToken.None).ConfigureAwait(false); else if (status == MediaStatus.Scrobble) response = await _httpClient.Post(TraktUris.ShowScrobble, data, Plugin.Instance.TraktResourcePool, CancellationToken.None).ConfigureAwait(false); return _jsonSerializer.DeserializeFromStream<TraktResponseDataContract>(response); }
private int CompareSpecials(Episode x, Episode y) { return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y)); }
private int GetSpecialCompareValue(Episode item) { // First sort by season number // Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough) var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000; // Second sort order is if it airs after the season if (item.AirsAfterSeasonNumber.HasValue) { val += 1000000; } // Third level is the episode number val += (item.AirsBeforeEpisodeNumber ?? 0) * 1000; // Finally, if that's still the same, last resort is the special number itself val += item.IndexNumber ?? 0; return val; }
private void FetchAdditionalPartInfo(Episode item, string xmlFile, CancellationToken cancellationToken) { using (var streamReader = new StreamReader(xmlFile, Encoding.UTF8)) { // Use XmlReader for best performance using (var reader = XmlReader.Create(streamReader, new XmlReaderSettings { CheckCharacters = false, IgnoreProcessingInstructions = true, IgnoreComments = true, ValidationType = ValidationType.None })) { reader.MoveToContent(); // Loop through each element while (reader.Read()) { cancellationToken.ThrowIfCancellationRequested(); if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "EpisodeName": { if (!item.LockedFields.Contains(MetadataFields.Name)) { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { item.Name += ", " + val; } } break; } case "Overview": { if (!item.LockedFields.Contains(MetadataFields.Overview)) { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { item.Overview += Environment.NewLine + Environment.NewLine + val; } } break; } case "Director": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { if (!item.LockedFields.Contains(MetadataFields.Cast)) { AddPeople(item, val, PersonType.Director); } } break; } case "GuestStars": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { if (!item.LockedFields.Contains(MetadataFields.Cast)) { AddGuestStars(item, val); } } break; } case "Writer": { var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { if (!item.LockedFields.Contains(MetadataFields.Cast)) { AddPeople(item, val, PersonType.Writer); } } break; } default: reader.Skip(); break; } } } } } }
private int CompareEpisodes(Episode x, Episode y) { var xValue = ((x.PhysicalSeasonNumber ?? -1) * 1000) + (x.IndexNumber ?? -1); var yValue = ((y.PhysicalSeasonNumber ?? -1) * 1000) + (y.IndexNumber ?? -1); return xValue.CompareTo(yValue); }
/// <summary> /// Fetches the episode data. /// </summary> /// <param name="seriesXml">The series XML.</param> /// <param name="episode">The episode.</param> /// <param name="seriesId">The series id.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> private async Task<ProviderRefreshStatus> FetchEpisodeData(XmlDocument seriesXml, Episode episode, string seriesId, CancellationToken cancellationToken) { var status = ProviderRefreshStatus.Success; if (episode.IndexNumber == null) { return status; } var seasonNumber = episode.ParentIndexNumber ?? TVUtils.GetSeasonNumberFromEpisodeFile(episode.Path); if (seasonNumber == null) { return status; } var usingAbsoluteData = false; var episodeNode = seriesXml.SelectSingleNode("//Episode[EpisodeNumber='" + episode.IndexNumber.Value + "'][SeasonNumber='" + seasonNumber.Value + "']"); if (episodeNode == null) { if (seasonNumber.Value == 1) { episodeNode = seriesXml.SelectSingleNode("//Episode[absolute_number='" + episode.IndexNumber.Value + "']"); usingAbsoluteData = true; } } // If still null, nothing we can do if (episodeNode == null) { return status; } var doc = new XmlDocument(); doc.LoadXml(episodeNode.OuterXml); if (!episode.HasImage(ImageType.Primary)) { var p = doc.SafeGetString("//filename"); if (p != null) { if (!Directory.Exists(episode.MetaLocation)) Directory.CreateDirectory(episode.MetaLocation); try { episode.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(episode, TVUtils.BannerUrl + p, Path.GetFileName(p), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken); } catch (HttpException) { status = ProviderRefreshStatus.CompletedWithErrors; } } } episode.Overview = doc.SafeGetString("//Overview"); if (usingAbsoluteData) episode.IndexNumber = doc.SafeGetInt32("//absolute_number", -1); if (episode.IndexNumber < 0) episode.IndexNumber = doc.SafeGetInt32("//EpisodeNumber"); episode.Name = doc.SafeGetString("//EpisodeName"); episode.CommunityRating = doc.SafeGetSingle("//Rating", -1, 10); var firstAired = doc.SafeGetString("//FirstAired"); DateTime airDate; if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850) { episode.PremiereDate = airDate.ToUniversalTime(); episode.ProductionYear = airDate.Year; } episode.People.Clear(); var actors = doc.SafeGetString("//GuestStars"); if (actors != null) { foreach (var person in actors.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(str => new PersonInfo { Type = PersonType.GuestStar, Name = str })) { episode.AddPerson(person); } } var directors = doc.SafeGetString("//Director"); if (directors != null) { foreach (var person in directors.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(str => new PersonInfo { Type = PersonType.Director, Name = str })) { episode.AddPerson(person); } } var writers = doc.SafeGetString("//Writer"); if (writers != null) { foreach (var person in writers.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(str => new PersonInfo { Type = PersonType.Writer, Name = str })) { episode.AddPerson(person); } } if (ConfigurationManager.Configuration.SaveLocalMeta) { if (!Directory.Exists(episode.MetaLocation)) Directory.CreateDirectory(episode.MetaLocation); var ms = new MemoryStream(); doc.Save(ms); await _providerManager.SaveToLibraryFilesystem(episode, Path.Combine(episode.MetaLocation, Path.GetFileNameWithoutExtension(episode.Path) + ".xml"), ms, cancellationToken).ConfigureAwait(false); } return status; }
/// <summary> /// Reports to trakt.tv that an episode is being watched. Or that Episode(s) have been watched. /// </summary> /// <param name="episode">The episode being watched</param> /// <param name="status">Enum indicating whether an episode is being watched or scrobbled</param> /// <param name="traktUser">The user that's watching the episode</param> /// <param name="progressPercent"></param> /// <returns>A List of standard TraktResponse Data Contracts</returns> public async Task<List<TraktScrobbleResponse>> SendEpisodeStatusUpdateAsync(Episode episode, MediaStatus status, TraktUser traktUser, float progressPercent) { var episodeDatas = new List<TraktScrobbleEpisode>(); if ((episode.IndexNumberEnd == null || episode.IndexNumberEnd == episode.IndexNumber) && !string.IsNullOrEmpty(episode.GetProviderId(MetadataProviders.Tvdb))) { episodeDatas.Add(new TraktScrobbleEpisode { AppDate = DateTime.Today.ToString("yyyy-MM-dd"), AppVersion = _appHost.ApplicationVersion.ToString(), Progress = progressPercent, Episode = new TraktEpisode { Ids = new TraktEpisodeId { Tvdb = episode.GetProviderId(MetadataProviders.Tvdb).ConvertToInt() }, } }); } // It's a multi-episode file. Add all episodes else if (episode.IndexNumber.HasValue) { episodeDatas.AddRange(Enumerable.Range(episode.IndexNumber.Value, ((episode.IndexNumberEnd ?? episode.IndexNumber).Value - episode.IndexNumber.Value) + 1) .Select(number => new TraktScrobbleEpisode { AppDate = DateTime.Today.ToString("yyyy-MM-dd"), AppVersion = _appHost.ApplicationVersion.ToString(), Progress = progressPercent, Episode = new TraktEpisode { Season = episode.GetSeasonNumber(), Number = number }, Show = new TraktShow { Title = episode.Series.Name, Year = episode.Series.ProductionYear, Ids = new TraktShowId { Tvdb = episode.Series.GetProviderId(MetadataProviders.Tvdb).ConvertToInt(), Imdb = episode.Series.GetProviderId(MetadataProviders.Imdb), TvRage = episode.Series.GetProviderId(MetadataProviders.TvRage).ConvertToInt() } } }).ToList()); } string url; switch (status) { case MediaStatus.Watching: url = TraktUris.ScrobbleStart; break; case MediaStatus.Paused: url = TraktUris.ScrobblePause; break; default: url = TraktUris.ScrobbleStop; break; } var responses = new List<TraktScrobbleResponse>(); foreach (var traktScrobbleEpisode in episodeDatas) { var response = await PostToTrakt(url, traktScrobbleEpisode, CancellationToken.None, traktUser); responses.Add(_jsonSerializer.DeserializeFromStream<TraktScrobbleResponse>(response)); } return responses; }
/// <summary> /// Sets the primary image path. /// </summary> /// <param name="item">The item.</param> /// <param name="parent">The parent.</param> /// <param name="metadataFolder">The metadata folder.</param> /// <param name="episodeFileName">Name of the episode file.</param> private void SetPrimaryImagePath(Episode item, Folder parent, string metadataFolder, string episodeFileName) { // Look for the image file in the metadata folder, and if found, set PrimaryImagePath var imageFiles = new[] { Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, ".jpg")), Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, ".png")) }; var file = parent.ResolveArgs.GetMetaFileByPath(imageFiles[0]) ?? parent.ResolveArgs.GetMetaFileByPath(imageFiles[1]); if (file != null) { item.PrimaryImagePath = file.FullName; } }
/// <summary> /// Adds the episode. /// </summary> /// <param name="series">The series.</param> /// <param name="seasonNumber">The season number.</param> /// <param name="episodeNumber">The episode number.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> private async Task AddEpisode(Series series, int seasonNumber, int episodeNumber, CancellationToken cancellationToken) { var season = series.Children.OfType<Season>() .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber); if (season == null) { season = await AddSeason(series, seasonNumber, cancellationToken).ConfigureAwait(false); } var name = string.Format("Episode {0}", episodeNumber.ToString(UsCulture)); var episode = new Episode { Name = name, IndexNumber = episodeNumber, ParentIndexNumber = seasonNumber, Parent = season, DisplayMediaType = typeof(Episode).Name, Id = (series.Id + seasonNumber.ToString(UsCulture) + name).GetMBId(typeof(Episode)) }; await season.AddChild(episode, cancellationToken).ConfigureAwait(false); await episode.RefreshMetadata(new MetadataRefreshOptions { }, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Validates the primary image path still exists /// </summary> /// <param name="episode">The episode.</param> /// <param name="metadataFolderPath">The metadata folder path.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> private void ValidateImage(Episode episode, string metadataFolderPath) { var path = episode.PrimaryImagePath; if (string.IsNullOrEmpty(path)) { return; } // Only validate images in the season/metadata folder if (!string.Equals(Path.GetDirectoryName(path), metadataFolderPath, StringComparison.OrdinalIgnoreCase)) { return; } if (episode.Parent.ResolveArgs.GetMetaFileByPath(path) == null) { episode.PrimaryImagePath = null; } }
/// <summary> /// Sets the primary image path. /// </summary> /// <param name="item">The item.</param> /// <param name="parent">The parent.</param> /// <param name="metadataFolder">The metadata folder.</param> /// <param name="episodeFileName">Name of the episode file.</param> private void SetPrimaryImagePath(Episode item, Folder parent, string metadataFolder, string episodeFileName) { foreach (var extension in BaseItem.SupportedImageExtensions) { var path = Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, extension)); var file = parent.ResolveArgs.GetMetaFileByPath(path); if (file != null) { item.PrimaryImagePath = file.FullName; return; } } var seasonFolder = Path.GetDirectoryName(item.Path); foreach (var extension in BaseItem.SupportedImageExtensions) { var imageFilename = Path.GetFileNameWithoutExtension(episodeFileName) + "-thumb" + extension; var path = Path.Combine(seasonFolder, imageFilename); var file = parent.ResolveArgs.GetMetaFileByPath(path); if (file != null) { item.PrimaryImagePath = file.FullName; return; } } }
/// <summary> /// Reports to trakt.tv that an episode is being watched. Or that Episode(s) have been watched. /// </summary> /// <param name="episode">The episode being watched</param> /// <param name="status">Enum indicating whether an episode is being watched or scrobbled</param> /// <param name="traktUser">The user that's watching the episode</param> /// <param name="progressPercent"></param> /// <returns>A List of standard TraktResponse Data Contracts</returns> public async Task<List<TraktScrobbleResponse>> SendEpisodeStatusUpdateAsync(Episode episode, MediaStatus status, TraktUser traktUser, float progressPercent) { var episodeDatas = new List<TraktScrobbleEpisode>(); var tvDbId = episode.GetProviderId(MetadataProviders.Tvdb); if (!string.IsNullOrEmpty(tvDbId) && (!episode.IndexNumber.HasValue || !episode.IndexNumberEnd.HasValue || episode.IndexNumberEnd <= episode.IndexNumber)) { episodeDatas.Add(new TraktScrobbleEpisode { AppDate = DateTime.Today.ToString("yyyy-MM-dd"), AppVersion = _appHost.ApplicationVersion.ToString(), Progress = progressPercent, Episode = new TraktEpisode { Ids = new TraktEpisodeId { Tvdb = tvDbId.ConvertToInt() }, } }); } else if (episode.IndexNumber.HasValue) { var indexNumber = episode.IndexNumber.Value; var finalNumber = (episode.IndexNumberEnd ?? episode.IndexNumber).Value; for (var number = indexNumber; number <= finalNumber; number++) { episodeDatas.Add(new TraktScrobbleEpisode { AppDate = DateTime.Today.ToString("yyyy-MM-dd"), AppVersion = _appHost.ApplicationVersion.ToString(), Progress = progressPercent, Episode = new TraktEpisode { Season = episode.GetSeasonNumber(), Number = number }, Show = new TraktShow { Title = episode.Series.Name, Year = episode.Series.ProductionYear, Ids = new TraktShowId { Tvdb = episode.Series.GetProviderId(MetadataProviders.Tvdb).ConvertToInt(), Imdb = episode.Series.GetProviderId(MetadataProviders.Imdb), TvRage = episode.Series.GetProviderId(MetadataProviders.TvRage).ConvertToInt() } } }); } } string url; switch (status) { case MediaStatus.Watching: url = TraktUris.ScrobbleStart; break; case MediaStatus.Paused: url = TraktUris.ScrobblePause; break; default: url = TraktUris.ScrobbleStop; break; } var responses = new List<TraktScrobbleResponse>(); foreach (var traktScrobbleEpisode in episodeDatas) { using (var response = await PostToTrakt(url, traktScrobbleEpisode, CancellationToken.None, traktUser)) { responses.Add(_jsonSerializer.DeserializeFromStream<TraktScrobbleResponse>(response)); } } return responses; }