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;
        }
Example #8
0
        /// <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;
            }
        }
Example #10
0
        /// <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;
        }
Example #15
0
        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;
        }
Example #21
0
        /// <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;
                }
            }
        }
Example #26
0
        /// <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;
        }