diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs index ddd07367aa..42cf493490 100644 --- a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs @@ -47,6 +47,8 @@ namespace MediaBrowser.Providers.TV public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) { + var seriesProviderIds = searchInfo.SeriesProviderIds; + var list = new List(); var identity = Identity.ParseIdentity(searchInfo.GetProviderId(FullIdKey)); @@ -59,14 +61,17 @@ namespace MediaBrowser.Providers.TV if (identity != null) { - await TvdbSeriesProvider.Current.EnsureSeriesInfo(identity.Value.SeriesId, searchInfo.MetadataLanguage, - cancellationToken).ConfigureAwait(false); + seriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + seriesProviderIds[MetadataProviders.Tvdb.ToString()] = identity.Value.SeriesId; + } - var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, identity.Value.SeriesId); + if (TvdbSeriesProvider.IsValidSeries(seriesProviderIds)) + { + var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); try { - var metadataResult = FetchEpisodeData(searchInfo, identity.Value, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken); + var metadataResult = FetchEpisodeData(searchInfo, identity, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken); if (metadataResult.HasMetadata) { @@ -247,11 +252,11 @@ namespace MediaBrowser.Providers.TV /// The series provider ids. /// The cancellation token. /// Task{System.Boolean}. - private MetadataResult FetchEpisodeData(EpisodeInfo id, Identity identity, string seriesDataPath, Dictionary seriesProviderIds, CancellationToken cancellationToken) + private MetadataResult FetchEpisodeData(EpisodeInfo id, Identity? identity, string seriesDataPath, Dictionary seriesProviderIds, CancellationToken cancellationToken) { - var episodeNumber = identity.EpisodeNumber; + var episodeNumber = identity.HasValue ? (identity.Value.EpisodeNumber) : id.IndexNumber.Value; var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(seriesProviderIds) ?? 0; - var seasonNumber = identity.SeasonIndex + seasonOffset; + var seasonNumber = identity.HasValue ? (identity.Value.SeasonIndex + seasonOffset) : id.ParentIndexNumber; string file; var usingAbsoluteData = false; @@ -294,7 +299,7 @@ namespace MediaBrowser.Providers.TV usingAbsoluteData = true; } - var end = identity.EpisodeNumberEnd ?? episodeNumber; + var end = identity.HasValue ? (identity.Value.EpisodeNumberEnd ?? episodeNumber) : (id.IndexNumberEnd ?? episodeNumber); episodeNumber++; while (episodeNumber <= end) diff --git a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs index e365511a5e..d88b39e106 100644 --- a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs @@ -66,24 +66,30 @@ namespace MediaBrowser.Providers.TV var season = (Season)item; var series = season.Series; - var identity = TvdbSeasonIdentityProvider.ParseIdentity(season.GetProviderId(TvdbSeasonIdentityProvider.FullIdKey)); - if (identity == null && series != null && season.IndexNumber.HasValue) + if (series != null && season.IndexNumber.HasValue && TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) { - identity = new TvdbSeasonIdentity(series.GetProviderId(MetadataProviders.Tvdb), season.IndexNumber.Value); - } + var seriesProviderIds = series.ProviderIds; + var seasonNumber = season.IndexNumber.Value; - if (identity != null && series != null) - { - var id = identity.Value; - await TvdbSeriesProvider.Current.EnsureSeriesInfo(id.SeriesId, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false); + var identity = TvdbSeasonIdentityProvider.ParseIdentity(season.GetProviderId(TvdbSeasonIdentityProvider.FullIdKey)); + if (identity == null) + { + identity = new TvdbSeasonIdentity(series.GetProviderId(MetadataProviders.Tvdb), seasonNumber); + } - // Process images - var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, id.SeriesId); + if (identity != null) + { + var id = identity.Value; + seasonNumber = AdjustForSeriesOffset(series, id.Index); + + seriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + seriesProviderIds[MetadataProviders.Tvdb.ToString()] = id.SeriesId; + } + + var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false); var path = Path.Combine(seriesDataPath, "banners.xml"); - var seasonNumber = AdjustForSeriesOffset(series, id.Index); - try { return GetImages(path, item.GetPreferredMetadataLanguage(), seasonNumber, cancellationToken); diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs index ae1933c7fa..40b647ceeb 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs @@ -62,23 +62,17 @@ namespace MediaBrowser.Providers.TV public async Task> GetImages(IHasImages item, CancellationToken cancellationToken) { - var series = (Series)item; - var seriesId = series.GetProviderId(MetadataProviders.Tvdb); - - if (!string.IsNullOrEmpty(seriesId)) + if (TvdbSeriesProvider.IsValidSeries(item.ProviderIds)) { var language = item.GetPreferredMetadataLanguage(); - await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesId, language, cancellationToken).ConfigureAwait(false); - - // Process images - var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId); + var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, language, cancellationToken).ConfigureAwait(false); var path = Path.Combine(seriesDataPath, "banners.xml"); try { - var seriesOffset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds); + var seriesOffset = TvdbSeriesProvider.GetSeriesOffset(item.ProviderIds); if (seriesOffset != null && seriesOffset.Value != 0) return TvdbSeasonImageProvider.GetImages(path, language, seriesOffset.Value + 1, cancellationToken); diff --git a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs index f9817af898..24ac7fca6b 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs @@ -59,56 +59,48 @@ namespace MediaBrowser.Providers.TV public async Task> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) { - var seriesId = searchInfo.GetProviderId(MetadataProviders.Tvdb); - - if (string.IsNullOrWhiteSpace(seriesId)) + if (IsValidSeries(searchInfo.ProviderIds)) { - return await FindSeries(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); - } + var metadata = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false); - var metadata = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false); - - var list = new List(); - - if (metadata.HasMetadata) - { - var res = new RemoteSearchResult + if (metadata.HasMetadata) { - Name = metadata.Item.Name, - PremiereDate = metadata.Item.PremiereDate, - ProductionYear = metadata.Item.ProductionYear, - ProviderIds = metadata.Item.ProviderIds, - SearchProviderName = Name - }; - - list.Add(res); + return new List + { + new RemoteSearchResult + { + Name = metadata.Item.Name, + PremiereDate = metadata.Item.PremiereDate, + ProductionYear = metadata.Item.ProductionYear, + ProviderIds = metadata.Item.ProviderIds, + SearchProviderName = Name + } + }; + } } - return list; + return await FindSeries(searchInfo.Name, searchInfo.Year, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); } public async Task> GetMetadata(SeriesInfo itemId, CancellationToken cancellationToken) { var result = new MetadataResult(); - var seriesId = itemId.GetProviderId(MetadataProviders.Tvdb); - - if (string.IsNullOrWhiteSpace(seriesId)) + if (!IsValidSeries(itemId.ProviderIds)) { await Identify(itemId).ConfigureAwait(false); - seriesId = itemId.GetProviderId(MetadataProviders.Tvdb); } - + cancellationToken.ThrowIfCancellationRequested(); - if (!string.IsNullOrWhiteSpace(seriesId)) + if (IsValidSeries(itemId.ProviderIds)) { - await EnsureSeriesInfo(seriesId, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false); + await EnsureSeriesInfo(itemId.ProviderIds, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false); result.Item = new Series(); result.HasMetadata = true; - FetchSeriesData(result, seriesId, cancellationToken); + FetchSeriesData(result, itemId.GetProviderId(MetadataProviders.Tvdb), cancellationToken); await FindAnimeSeriesIndex(result.Item, itemId).ConfigureAwait(false); } @@ -168,7 +160,7 @@ namespace MediaBrowser.Providers.TV cancellationToken.ThrowIfCancellationRequested(); result.ResetPeople(); - + FetchActors(result, actorsXmlPath); } @@ -239,7 +231,7 @@ namespace MediaBrowser.Providers.TV if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase)) { - _fileSystem.CopyFile(downloadLangaugeXmlFile, saveAsLanguageXmlFile, true); + _fileSystem.CopyFile(downloadLangaugeXmlFile, saveAsLanguageXmlFile, true); } await ExtractEpisodes(seriesDataPath, downloadLangaugeXmlFile, lastTvDbUpdateTime).ConfigureAwait(false); @@ -250,62 +242,94 @@ namespace MediaBrowser.Providers.TV return _config.GetConfiguration("tvdb"); } - private readonly Task _cachedTask = Task.FromResult(true); - internal Task EnsureSeriesInfo(string seriesId, string preferredMetadataLanguage, CancellationToken cancellationToken) + internal static bool IsValidSeries(Dictionary seriesProviderIds) { - var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesId); - - _fileSystem.CreateDirectory(seriesDataPath); - - var files = _fileSystem.GetFiles(seriesDataPath) - .ToList(); - - var seriesXmlFilename = preferredMetadataLanguage + ".xml"; - - var download = false; - var automaticUpdatesEnabled = GetTvDbOptions().EnableAutomaticUpdates; - - const int cacheDays = 2; - - var seriesFile = files.FirstOrDefault(i => string.Equals(seriesXmlFilename, i.Name, StringComparison.OrdinalIgnoreCase)); - // No need to check age if automatic updates are enabled - if (seriesFile == null || !seriesFile.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(seriesFile)).TotalDays > cacheDays)) + string id; + if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out id)) { - download = true; + return true; + } + //if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id)) + //{ + // return true; + //} + return false; + } + + internal async Task EnsureSeriesInfo(Dictionary seriesProviderIds, string preferredMetadataLanguage, CancellationToken cancellationToken) + { + string seriesId; + if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesId)) + { + var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesId); + + // Only download if not already there + // The post-scan task will take care of updates so we don't need to re-download here + if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage)) + { + await DownloadSeriesZip(seriesId, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + } + + return seriesDataPath; } - var actorsXml = files.FirstOrDefault(i => string.Equals("actors.xml", i.Name, StringComparison.OrdinalIgnoreCase)); - // No need to check age if automatic updates are enabled - if (actorsXml == null || !actorsXml.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(actorsXml)).TotalDays > cacheDays)) - { - download = true; - } + return null; + } - var bannersXml = files.FirstOrDefault(i => string.Equals("banners.xml", i.Name, StringComparison.OrdinalIgnoreCase)); - // No need to check age if automatic updates are enabled - if (bannersXml == null || !bannersXml.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(bannersXml)).TotalDays > cacheDays)) + private bool IsCacheValid(string seriesDataPath, string preferredMetadataLanguage) + { + try { - download = true; - } + var files = _fileSystem.GetFiles(seriesDataPath) + .ToList(); - // Only download if not already there - // The post-scan task will take care of updates so we don't need to re-download here - if (download) + var seriesXmlFilename = preferredMetadataLanguage + ".xml"; + + var automaticUpdatesEnabled = GetTvDbOptions().EnableAutomaticUpdates; + + const int cacheDays = 2; + + var seriesFile = files.FirstOrDefault(i => string.Equals(seriesXmlFilename, i.Name, StringComparison.OrdinalIgnoreCase)); + // No need to check age if automatic updates are enabled + if (seriesFile == null || !seriesFile.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(seriesFile)).TotalDays > cacheDays)) + { + return false; + } + + var actorsXml = files.FirstOrDefault(i => string.Equals("actors.xml", i.Name, StringComparison.OrdinalIgnoreCase)); + // No need to check age if automatic updates are enabled + if (actorsXml == null || !actorsXml.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(actorsXml)).TotalDays > cacheDays)) + { + return false; + } + + var bannersXml = files.FirstOrDefault(i => string.Equals("banners.xml", i.Name, StringComparison.OrdinalIgnoreCase)); + // No need to check age if automatic updates are enabled + if (bannersXml == null || !bannersXml.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(bannersXml)).TotalDays > cacheDays)) + { + return false; + } + return true; + } + catch (DirectoryNotFoundException) { - return DownloadSeriesZip(seriesId, seriesDataPath, null, preferredMetadataLanguage, cancellationToken); + return false; + } + catch (FileNotFoundException) + { + return false; } - - return _cachedTask; } /// /// Finds the series. /// /// The name. + /// The year. /// The language. /// The cancellation token. /// Task{System.String}. - private async Task> FindSeries(string name, string language, CancellationToken cancellationToken) + private async Task> FindSeries(string name, int? year, string language, CancellationToken cancellationToken) { var results = (await FindSeriesInternal(name, language, cancellationToken).ConfigureAwait(false)).ToList(); @@ -320,7 +344,16 @@ namespace MediaBrowser.Providers.TV } } - return results; + return results.Where(i => + { + if (year.HasValue && i.ProductionYear.HasValue) + { + // Allow one year tolerance + return Math.Abs(year.Value - i.ProductionYear.Value) <= 1; + } + + return true; + }); } private async Task> FindSeriesInternal(string name, string language, CancellationToken cancellationToken) @@ -1108,7 +1141,7 @@ namespace MediaBrowser.Providers.TV var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber, episodeNumber)); // Only save the file if not already there, or if the episode has changed - if (hasEpisodeChanged || !_fileSystem.FileExists(file)) + if (hasEpisodeChanged || !_fileSystem.FileExists(file)) { using (var writer = XmlWriter.Create(file, new XmlWriterSettings { @@ -1125,7 +1158,7 @@ namespace MediaBrowser.Providers.TV file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", absoluteNumber)); // Only save the file if not already there, or if the episode has changed - if (hasEpisodeChanged || !_fileSystem.FileExists(file)) + if (hasEpisodeChanged || !_fileSystem.FileExists(file)) { using (var writer = XmlWriter.Create(file, new XmlWriterSettings { @@ -1242,17 +1275,19 @@ namespace MediaBrowser.Providers.TV public async Task Identify(SeriesInfo info) { - if (string.IsNullOrEmpty(info.GetProviderId(MetadataProviders.Tvdb))) + if (!string.IsNullOrWhiteSpace(info.GetProviderId(MetadataProviders.Tvdb))) { - var srch = await FindSeries(info.Name, info.MetadataLanguage, CancellationToken.None).ConfigureAwait(false); + return; + } - var entry = srch.FirstOrDefault(); + var srch = await FindSeries(info.Name, info.Year, info.MetadataLanguage, CancellationToken.None).ConfigureAwait(false); - if (entry != null) - { - var id = entry.GetProviderId(MetadataProviders.Tvdb); - info.SetProviderId(MetadataProviders.Tvdb, id); - } + var entry = srch.FirstOrDefault(); + + if (entry != null) + { + var id = entry.GetProviderId(MetadataProviders.Tvdb); + info.SetProviderId(MetadataProviders.Tvdb, id); } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 0d49607953..cdfe9ebef4 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -607,8 +607,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV throw new ArgumentNullException("timer"); } + if (string.IsNullOrWhiteSpace(timer.ProgramId)) + { + throw new InvalidOperationException("timer.ProgramId is null. Cannot record."); + } + var info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); + if (info == null) + { + throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId)); + } + var recordPath = RecordingPath; if (info.IsMovie)