コード例 #1
0
        private int AdjustForSeriesOffset(Series series, int seasonNumber)
        {
            var offset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds);

            if (offset != null)
            {
                return((int)(seasonNumber + offset));
            }

            return(seasonNumber);
        }
コード例 #2
0
        public Task <IEnumerable <RemoteImageInfo> > GetImages(IHasImages item, CancellationToken cancellationToken)
        {
            var episode = (Episode)item;
            var series  = episode.Series;

            if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
            {
                // Process images
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds);
                var indexOffset    = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds) ?? 0;

                var files = TvdbEpisodeProvider.Current.GetEpisodeXmlFiles(episode.ParentIndexNumber + indexOffset, episode.IndexNumber, episode.IndexNumberEnd, seriesDataPath);

                var result = files.Select(i => GetImageInfo(i, cancellationToken))
                             .Where(i => i != null);

                return(Task.FromResult(result));
            }

            return(Task.FromResult <IEnumerable <RemoteImageInfo> >(new RemoteImageInfo[] { }));
        }
コード例 #3
0
        public async Task <IEnumerable <RemoteImageInfo> > GetImages(IHasImages item, CancellationToken cancellationToken)
        {
            var series   = (Series)item;
            var seriesId = series.GetProviderId(MetadataProviders.Tvdb);

            if (!string.IsNullOrEmpty(seriesId))
            {
                var language = item.GetPreferredMetadataLanguage();

                await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesId, language, cancellationToken).ConfigureAwait(false);

                // Process images
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);

                var path = Path.Combine(seriesDataPath, "banners.xml");

                try
                {
                    var seriesOffset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds);
                    if (seriesOffset != null && seriesOffset.Value != 0)
                    {
                        return(TvdbSeasonImageProvider.GetImages(path, language, seriesOffset.Value + 1, cancellationToken));
                    }

                    return(GetImages(path, language, cancellationToken));
                }
                catch (FileNotFoundException)
                {
                    // No tvdb data yet. Don't blow up
                }
                catch (DirectoryNotFoundException)
                {
                    // No tvdb data yet. Don't blow up
                }
            }

            return(new RemoteImageInfo[] { });
        }
コード例 #4
0
        /// <summary>
        /// Removes the obsolete or missing seasons.
        /// </summary>
        /// <param name="series">The series.</param>
        /// <param name="episodeLookup">The episode lookup.</param>
        /// <returns>Task{System.Boolean}.</returns>
        private async Task <bool> RemoveObsoleteOrMissingSeasons(IEnumerable <Series> series,
                                                                 IEnumerable <Tuple <int, int> > episodeLookup)
        {
            var existingSeasons = (from s in series
                                   let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
                                                      from c in s.Children.OfType <Season>()
                                                      select new { SeasonOffset = seasonOffset, Season = c })
                                  .ToList();

            var physicalSeasons = existingSeasons
                                  .Where(i => i.Season.LocationType != LocationType.Virtual)
                                  .ToList();

            var virtualSeasons = existingSeasons
                                 .Where(i => i.Season.LocationType == LocationType.Virtual)
                                 .ToList();

            var seasonsToRemove = virtualSeasons
                                  .Where(i =>
            {
                if (i.Season.IndexNumber.HasValue)
                {
                    var seasonNumber = i.Season.IndexNumber.Value + i.SeasonOffset;

                    // If there's a physical season with the same number, delete it
                    if (physicalSeasons.Any(p => p.Season.IndexNumber.HasValue && (p.Season.IndexNumber.Value + p.SeasonOffset) == seasonNumber))
                    {
                        return(true);
                    }

                    // If the season no longer exists in the remote lookup, delete it
                    if (episodeLookup.All(e => e.Item1 != seasonNumber))
                    {
                        return(true);
                    }

                    return(false);
                }

                // Season does not have a number
                // Remove if there are no episodes directly in series without a season number
                return(i.Season.Series.GetRecursiveChildren().OfType <Episode>().All(s => s.ParentIndexNumber.HasValue || !s.IsInSeasonFolder));
            })
                                  .ToList();

            var hasChanges = false;

            foreach (var seasonToRemove in seasonsToRemove.Select(s => s.Season))
            {
                _logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber);

                await seasonToRemove.Delete(new DeleteOptions
                {
                    DeleteFileLocation = true
                }).ConfigureAwait(false);

                hasChanges = true;
            }

            return(hasChanges);
        }
コード例 #5
0
        /// <summary>
        /// Removes the virtual entry after a corresponding physical version has been added
        /// </summary>
        private async Task <bool> RemoveObsoleteOrMissingEpisodes(IEnumerable <Series> series,
                                                                  IEnumerable <Tuple <int, int> > episodeLookup)
        {
            var existingEpisodes = (from s in series
                                    let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
                                                       from c in s.GetRecursiveChildren().OfType <Episode>()
                                                       select new { SeasonOffset = seasonOffset, Episode = c })
                                   .ToList();

            var physicalEpisodes = existingEpisodes
                                   .Where(i => i.Episode.LocationType != LocationType.Virtual)
                                   .ToList();

            var virtualEpisodes = existingEpisodes
                                  .Where(i => i.Episode.LocationType == LocationType.Virtual)
                                  .ToList();

            var episodesToRemove = virtualEpisodes
                                   .Where(i =>
            {
                if (i.Episode.IndexNumber.HasValue && i.Episode.ParentIndexNumber.HasValue)
                {
                    var seasonNumber  = i.Episode.ParentIndexNumber.Value + i.SeasonOffset;
                    var episodeNumber = i.Episode.IndexNumber.Value;

                    // If there's a physical episode with the same season and episode number, delete it
                    if (physicalEpisodes.Any(p =>
                                             p.Episode.ParentIndexNumber.HasValue && (p.Episode.ParentIndexNumber.Value + p.SeasonOffset) == seasonNumber &&
                                             p.Episode.ContainsEpisodeNumber(episodeNumber)))
                    {
                        return(true);
                    }

                    // If the episode no longer exists in the remote lookup, delete it
                    if (!episodeLookup.Any(e => e.Item1 == seasonNumber && e.Item2 == episodeNumber))
                    {
                        return(true);
                    }

                    return(false);
                }

                return(true);
            })
                                   .ToList();

            var hasChanges = false;

            foreach (var episodeToRemove in episodesToRemove.Select(e => e.Episode))
            {
                _logger.Info("Removing missing/unaired episode {0} {1}x{2}", episodeToRemove.Series.Name, episodeToRemove.ParentIndexNumber, episodeToRemove.IndexNumber);

                await episodeToRemove.Delete(new DeleteOptions
                {
                    DeleteFileLocation = true
                }).ConfigureAwait(false);

                hasChanges = true;
            }

            return(hasChanges);
        }
コード例 #6
0
        private Series DetermineAppropriateSeries(IEnumerable <Series> series, int seasonNumber)
        {
            var seriesAndOffsets = series.Select(s => new { Series = s, SeasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) }).ToList();

            var bestMatch = seriesAndOffsets.FirstOrDefault(s => s.Series.GetRecursiveChildren().OfType <Season>().Any(season => (season.IndexNumber + s.SeasonOffset) == seasonNumber)) ??
                            seriesAndOffsets.FirstOrDefault(s => s.Series.GetRecursiveChildren().OfType <Season>().Any(season => (season.IndexNumber + s.SeasonOffset) == 1)) ??
                            seriesAndOffsets.OrderBy(s => s.Series.GetRecursiveChildren().OfType <Season>().Select(season => season.IndexNumber + s.SeasonOffset).Min()).First();

            return(bestMatch.Series);
        }
コード例 #7
0
        /// <summary>
        /// Adds the missing episodes.
        /// </summary>
        /// <param name="series">The series.</param>
        /// <param name="seriesHasBadData">if set to <c>true</c> [series has bad data].</param>
        /// <param name="seriesDataPath">The series data path.</param>
        /// <param name="episodeLookup">The episode lookup.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Task.</returns>
        private async Task <bool> AddMissingEpisodes(List <Series> series,
                                                     bool seriesHasBadData,
                                                     string seriesDataPath,
                                                     IEnumerable <Tuple <int, int> > episodeLookup,
                                                     CancellationToken cancellationToken)
        {
            var existingEpisodes = (from s in series
                                    let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
                                                       from c in s.GetRecursiveChildren().OfType <Episode>()
                                                       select new Tuple <int, Episode>((c.ParentIndexNumber ?? 0) + seasonOffset, c))
                                   .ToList();

            var lookup = episodeLookup as IList <Tuple <int, int> > ?? episodeLookup.ToList();

            var seasonCounts = (from e in lookup
                                group e by e.Item1 into g
                                select g)
                               .ToDictionary(g => g.Key, g => g.Count());

            var hasChanges = false;

            foreach (var tuple in lookup)
            {
                if (tuple.Item1 <= 0)
                {
                    // Ignore season zeros
                    continue;
                }

                if (tuple.Item2 <= 0)
                {
                    // Ignore episode zeros
                    continue;
                }

                var existingEpisode = GetExistingEpisode(existingEpisodes, seasonCounts, tuple);

                if (existingEpisode != null)
                {
                    continue;
                }

                var airDate = GetAirDate(seriesDataPath, tuple.Item1, tuple.Item2);

                if (!airDate.HasValue)
                {
                    continue;
                }
                var now = DateTime.UtcNow;

                var targetSeries = DetermineAppropriateSeries(series, tuple.Item1);
                var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(targetSeries.ProviderIds) ?? ((targetSeries.AnimeSeriesIndex ?? 1) - 1);

                if (airDate.Value < now)
                {
                    // Be conservative here to avoid creating missing episodes for ones they already have
                    if (!seriesHasBadData)
                    {
                        // tvdb has a lot of nearly blank episodes
                        _logger.Info("Creating virtual missing episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2);
                        await AddEpisode(targetSeries, tuple.Item1 - seasonOffset, tuple.Item2, cancellationToken).ConfigureAwait(false);

                        hasChanges = true;
                    }
                }
                else if (airDate.Value > now)
                {
                    // tvdb has a lot of nearly blank episodes
                    _logger.Info("Creating virtual unaired episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2);
                    await AddEpisode(targetSeries, tuple.Item1 - seasonOffset, tuple.Item2, cancellationToken).ConfigureAwait(false);

                    hasChanges = true;
                }
            }

            return(hasChanges);
        }
コード例 #8
0
        /// <summary>
        /// Removes the obsolete or missing seasons.
        /// </summary>
        /// <param name="series">The series.</param>
        /// <param name="episodeLookup">The episode lookup.</param>
        /// <param name="forceRemoveAll">if set to <c>true</c> [force remove all].</param>
        /// <returns>Task{System.Boolean}.</returns>
        private async Task <bool> RemoveObsoleteOrMissingSeasons(IEnumerable <Series> series,
                                                                 IEnumerable <Tuple <int, int> > episodeLookup,
                                                                 bool forceRemoveAll)
        {
            var existingSeasons = (from s in series
                                   let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1)
                                                      from c in s.Children.OfType <Season>()
                                                      select new { SeasonOffset = seasonOffset, Season = c })
                                  .ToList();

            var physicalSeasons = existingSeasons
                                  .Where(i => i.Season.LocationType != LocationType.Virtual)
                                  .ToList();

            var virtualSeasons = existingSeasons
                                 .Where(i => i.Season.LocationType == LocationType.Virtual)
                                 .ToList();

            var seasonsToRemove = virtualSeasons
                                  .Where(i =>
            {
                if (forceRemoveAll)
                {
                    return(true);
                }

                if (i.Season.IndexNumber.HasValue)
                {
                    var seasonNumber = i.Season.IndexNumber.Value + i.SeasonOffset;

                    // If there's a physical season with the same number, delete it
                    if (physicalSeasons.Any(p => p.Season.IndexNumber.HasValue && (p.Season.IndexNumber.Value + p.SeasonOffset) == seasonNumber))
                    {
                        return(true);
                    }

                    // If the season no longer exists in the remote lookup, delete it
                    if (episodeLookup.All(e => e.Item1 != seasonNumber))
                    {
                        return(true);
                    }

                    return(false);
                }

                return(true);
            })
                                  .ToList();

            var hasChanges = false;

            foreach (var seasonToRemove in seasonsToRemove.Select(s => s.Season))
            {
                _logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber);

                await _libraryManager.DeleteItem(seasonToRemove).ConfigureAwait(false);

                hasChanges = true;
            }

            return(hasChanges);
        }
コード例 #9
0
ファイル: TvdbEpisodeProvider.cs プロジェクト: webcris77/Emby
        public async Task <MetadataResult <Episode> > GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
        {
            var identity = Identity.ParseIdentity(searchInfo.GetProviderId(FullIdKey));

            if (identity == null)
            {
                await Identify(searchInfo).ConfigureAwait(false);

                identity = Identity.ParseIdentity(searchInfo.GetProviderId(FullIdKey));
            }

            var result = new MetadataResult <Episode>();

            if (identity != null)
            {
                var seriesProviderIds = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
                seriesProviderIds[MetadataProviders.Tvdb.ToString()] = identity.Value.SeriesId;
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds);

                var searchNumbers = new EpisodeNumbers();
                searchNumbers.EpisodeNumber = identity.Value.EpisodeNumber;
                var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(searchInfo.SeriesProviderIds) ?? 0;
                searchNumbers.SeasonNumber     = identity.Value.SeasonIndex + seasonOffset;
                searchNumbers.EpisodeNumberEnd = identity.Value.EpisodeNumberEnd ?? searchNumbers.EpisodeNumber;

                try
                {
                    result = FetchEpisodeData(searchInfo, searchNumbers, seriesDataPath, cancellationToken);
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
                catch (DirectoryNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }
            else if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) && searchInfo.IndexNumber.HasValue)
            {
                var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, searchInfo.SeriesProviderIds);

                var searchNumbers = new EpisodeNumbers();
                searchNumbers.EpisodeNumber    = searchInfo.IndexNumber.Value;
                searchNumbers.SeasonNumber     = searchInfo.ParentIndexNumber;
                searchNumbers.EpisodeNumberEnd = searchInfo.IndexNumberEnd ?? searchNumbers.EpisodeNumber;

                try
                {
                    result = FetchEpisodeData(searchInfo, searchNumbers, seriesDataPath, cancellationToken);
                }
                catch (FileNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
                catch (DirectoryNotFoundException)
                {
                    // Don't fail the provider because this will just keep on going and going.
                }
            }
            else
            {
                _logger.Debug("No series identity found for {0}", searchInfo.Name);
            }

            return(result);
        }
コード例 #10
0
        /// <summary>
        /// Fetches the episode data.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <param name="identity">The identity.</param>
        /// <param name="seriesDataPath">The series data path.</param>
        /// <param name="seriesProviderIds">The series provider ids.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Task{System.Boolean}.</returns>
        private MetadataResult <Episode> FetchEpisodeData(EpisodeInfo id, Identity?identity, string seriesDataPath, Dictionary <string, string> seriesProviderIds, CancellationToken cancellationToken)
        {
            var episodeNumber = identity.HasValue ? (identity.Value.EpisodeNumber) : id.IndexNumber.Value;
            var seasonOffset  = TvdbSeriesProvider.GetSeriesOffset(seriesProviderIds) ?? 0;
            var seasonNumber  = identity.HasValue ? (identity.Value.SeasonIndex + seasonOffset) : id.ParentIndexNumber;

            string file;
            var    usingAbsoluteData = false;

            var result = new MetadataResult <Episode>()
            {
                Item = new Episode
                {
                    IndexNumber       = id.IndexNumber,
                    ParentIndexNumber = id.ParentIndexNumber,
                    IndexNumberEnd    = id.IndexNumberEnd
                }
            };

            try
            {
                if (seasonNumber != null)
                {
                    file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber));
                    FetchMainEpisodeInfo(result, file, cancellationToken);

                    result.HasMetadata = true;
                }
            }
            catch (FileNotFoundException)
            {
                // Could be using absolute numbering
                if (seasonNumber.HasValue && seasonNumber.Value != 1)
                {
                    throw;
                }
            }

            if (!result.HasMetadata)
            {
                file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber));

                FetchMainEpisodeInfo(result, file, cancellationToken);
                result.HasMetadata = true;
                usingAbsoluteData  = true;
            }

            var end = identity.HasValue ? (identity.Value.EpisodeNumberEnd ?? episodeNumber) : (id.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(result, file, cancellationToken);
                }
                catch (FileNotFoundException)
                {
                    break;
                }
                catch (DirectoryNotFoundException)
                {
                    break;
                }

                episodeNumber++;
            }

            return(result);
        }
コード例 #11
0
        /// <summary>
        /// Fetches the episode data.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <param name="seriesDataPath">The series data path.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Task{System.Boolean}.</returns>
        private Episode FetchEpisodeData(EpisodeInfo id, string seriesDataPath, Dictionary <string, string> seriesProviderIds, CancellationToken cancellationToken)
        {
            if (id.IndexNumber == null)
            {
                return(null);
            }

            var episodeNumber = id.IndexNumber.Value;
            var seasonOffset  = TvdbSeriesProvider.GetSeriesOffset(seriesProviderIds) ?? 0;
            var seasonNumber  = id.ParentIndexNumber + seasonOffset;

            if (seasonNumber == null)
            {
                return(null);
            }

            var file              = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber));
            var success           = false;
            var usingAbsoluteData = false;

            var episode = new Episode
            {
                IndexNumber       = id.IndexNumber,
                ParentIndexNumber = id.ParentIndexNumber,
                IndexNumberEnd    = id.IndexNumberEnd
            };

            try
            {
                FetchMainEpisodeInfo(episode, file, cancellationToken);

                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));

                FetchMainEpisodeInfo(episode, file, cancellationToken);
                usingAbsoluteData = true;
            }

            var end = id.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(success ? episode : null);
        }