Explorar el Código

Backport pull request #11719 from jellyfin/release-10.9.z

Move NFO series season name parsing to own local provider

Original-merge: a53ea029fade01a18e8e525543b5cda14e16533a

Merged-by: joshuaboniface <joshua@boniface.me>

Backported-by: Joshua M. Boniface <joshua@boniface.me>
Shadowghost hace 1 año
padre
commit
c0364fc766

+ 10 - 20
Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs

@@ -54,7 +54,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                 {
                     IndexNumber = seasonParserResult.SeasonNumber,
                     SeriesId = series.Id,
-                    SeriesName = series.Name
+                    SeriesName = series.Name,
+                    Path = seasonParserResult.IsSeasonFolder ? path : args.Parent.Path
                 };
 
                 if (!season.IndexNumber.HasValue || !seasonParserResult.IsSeasonFolder)
@@ -78,27 +79,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                     }
                 }
 
-                if (season.IndexNumber.HasValue)
+                if (season.IndexNumber.HasValue && string.IsNullOrEmpty(season.Name))
                 {
                     var seasonNumber = season.IndexNumber.Value;
-                    if (string.IsNullOrEmpty(season.Name))
-                    {
-                        var seasonNames = series.GetSeasonNames();
-                        if (seasonNames.TryGetValue(seasonNumber, out var seasonName))
-                        {
-                            season.Name = seasonName;
-                        }
-                        else
-                        {
-                            season.Name = seasonNumber == 0 ?
-                                args.LibraryOptions.SeasonZeroDisplayName :
-                                string.Format(
-                                    CultureInfo.InvariantCulture,
-                                    _localization.GetLocalizedString("NameSeasonNumber"),
-                                    seasonNumber,
-                                    args.LibraryOptions.PreferredMetadataLanguage);
-                        }
-                    }
+                    season.Name = seasonNumber == 0 ?
+                        args.LibraryOptions.SeasonZeroDisplayName :
+                        string.Format(
+                            CultureInfo.InvariantCulture,
+                            _localization.GetLocalizedString("NameSeasonNumber"),
+                            seasonNumber,
+                            args.LibraryOptions.PreferredMetadataLanguage);
                 }
 
                 return season;

+ 0 - 23
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -25,12 +25,9 @@ namespace MediaBrowser.Controller.Entities.TV
     /// </summary>
     public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IMetadataContainer
     {
-        private readonly Dictionary<int, string> _seasonNames;
-
         public Series()
         {
             AirDays = Array.Empty<DayOfWeek>();
-            _seasonNames = new Dictionary<int, string>();
         }
 
         public DayOfWeek[] AirDays { get; set; }
@@ -212,26 +209,6 @@ namespace MediaBrowser.Controller.Entities.TV
             return LibraryManager.GetItemList(query);
         }
 
-        public Dictionary<int, string> GetSeasonNames()
-        {
-            var newSeasons = Children.OfType<Season>()
-                .Where(s => s.IndexNumber.HasValue)
-                .Where(s => !_seasonNames.ContainsKey(s.IndexNumber.Value))
-                .DistinctBy(s => s.IndexNumber);
-
-            foreach (var season in newSeasons)
-            {
-                SetSeasonName(season.IndexNumber.Value, season.Name);
-            }
-
-            return _seasonNames;
-        }
-
-        public void SetSeasonName(int index, string name)
-        {
-            _seasonNames[index] = name;
-        }
-
         private void SetSeasonQueryOptions(InternalItemsQuery query, User user)
         {
             var seriesKey = GetUniqueSeriesKey(this);

+ 6 - 0
MediaBrowser.Controller/Providers/ItemInfo.cs

@@ -11,6 +11,8 @@ namespace MediaBrowser.Controller.Providers
         public ItemInfo(BaseItem item)
         {
             Path = item.Path;
+            ParentId = item.ParentId;
+            IndexNumber = item.IndexNumber;
             ContainingFolderPath = item.ContainingFolderPath;
             IsInMixedFolder = item.IsInMixedFolder;
 
@@ -27,6 +29,10 @@ namespace MediaBrowser.Controller.Providers
 
         public string Path { get; set; }
 
+        public Guid ParentId { get; set; }
+
+        public int? IndexNumber { get; set; }
+
         public string ContainingFolderPath { get; set; }
 
         public VideoType VideoType { get; set; }

+ 4 - 35
MediaBrowser.Providers/TV/SeriesMetadataService.cs

@@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.TV
 
             RemoveObsoleteEpisodes(item);
             RemoveObsoleteSeasons(item);
-            await UpdateAndCreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
+            await CreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
         }
 
         /// <inheritdoc />
@@ -88,24 +88,6 @@ namespace MediaBrowser.Providers.TV
 
             var sourceItem = source.Item;
             var targetItem = target.Item;
-            var sourceSeasonNames = sourceItem.GetSeasonNames();
-            var targetSeasonNames = targetItem.GetSeasonNames();
-
-            if (replaceData)
-            {
-                foreach (var (number, name) in sourceSeasonNames)
-                {
-                    targetItem.SetSeasonName(number, name);
-                }
-            }
-            else
-            {
-                var newSeasons = sourceSeasonNames.Where(s => !targetSeasonNames.ContainsKey(s.Key));
-                foreach (var (number, name) in newSeasons)
-                {
-                    targetItem.SetSeasonName(number, name);
-                }
-            }
 
             if (replaceData || string.IsNullOrEmpty(targetItem.AirTime))
             {
@@ -218,14 +200,12 @@ namespace MediaBrowser.Providers.TV
         /// <summary>
         /// Creates seasons for all episodes if they don't exist.
         /// If no season number can be determined, a dummy season will be created.
-        /// Updates seasons names.
         /// </summary>
         /// <param name="series">The series.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>The async task.</returns>
-        private async Task UpdateAndCreateSeasonsAsync(Series series, CancellationToken cancellationToken)
+        private async Task CreateSeasonsAsync(Series series, CancellationToken cancellationToken)
         {
-            var seasonNames = series.GetSeasonNames();
             var seriesChildren = series.GetRecursiveChildren(i => i is Episode || i is Season);
             var seasons = seriesChildren.OfType<Season>().ToList();
             var uniqueSeasonNumbers = seriesChildren
@@ -237,23 +217,12 @@ namespace MediaBrowser.Providers.TV
             foreach (var seasonNumber in uniqueSeasonNumbers)
             {
                 // Null season numbers will have a 'dummy' season created because seasons are always required.
-                var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber);
-
-                if (!seasonNumber.HasValue || !seasonNames.TryGetValue(seasonNumber.Value, out var seasonName))
-                {
-                    seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber);
-                }
-
-                if (existingSeason is null)
+                if (!seasons.Any(i => i.IndexNumber == seasonNumber))
                 {
+                    var seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber);
                     var season = await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false);
                     series.AddChild(season);
                 }
-                else if (!existingSeason.LockedFields.Contains(MetadataField.Name) && !string.Equals(existingSeason.Name, seasonName, StringComparison.Ordinal))
-                {
-                    existingSeason.Name = seasonName;
-                    await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
-                }
             }
         }
 

+ 3 - 12
MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs

@@ -100,19 +100,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                         break;
                     }
 
+                // Season names are processed by SeriesNfoSeasonParser
                 case "namedseason":
-                    {
-                        var parsed = int.TryParse(reader.GetAttribute("number"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seasonNumber);
-                        var name = reader.ReadElementContentAsString();
-
-                        if (!string.IsNullOrWhiteSpace(name) && parsed)
-                        {
-                            item.SetSeasonName(seasonNumber, name);
-                        }
-
-                        break;
-                    }
-
+                    reader.Skip();
+                    break;
                 default:
                     base.FetchDataFromXmlNode(reader, itemResult);
                     break;

+ 60 - 0
MediaBrowser.XbmcMetadata/Parsers/SeriesNfoSeasonParser.cs

@@ -0,0 +1,60 @@
+using System.Globalization;
+using System.Xml;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.XbmcMetadata.Parsers
+{
+    /// <summary>
+    /// NFO parser for seasons based on series NFO.
+    /// </summary>
+    public class SeriesNfoSeasonParser : BaseNfoParser<Season>
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SeriesNfoSeasonParser"/> class.
+        /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+        /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
+        /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+        /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
+        public SeriesNfoSeasonParser(
+            ILogger logger,
+            IConfigurationManager config,
+            IProviderManager providerManager,
+            IUserManager userManager,
+            IUserDataManager userDataManager,
+            IDirectoryService directoryService)
+            : base(logger, config, providerManager, userManager, userDataManager, directoryService)
+        {
+        }
+
+        /// <inheritdoc />
+        protected override bool SupportsUrlAfterClosingXmlTag => true;
+
+        /// <inheritdoc />
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Season> itemResult)
+        {
+            var item = itemResult.Item;
+
+            if (reader.Name == "namedseason")
+            {
+                var parsed = int.TryParse(reader.GetAttribute("number"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seasonNumber);
+                var name = reader.ReadElementContentAsString();
+
+                if (parsed && !string.IsNullOrWhiteSpace(name) && item.IndexNumber.HasValue && seasonNumber == item.IndexNumber.Value)
+                {
+                    item.Name = name;
+                }
+            }
+            else
+            {
+                reader.Skip();
+            }
+        }
+    }
+}

+ 4 - 1
MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs

@@ -42,7 +42,10 @@ namespace MediaBrowser.XbmcMetadata.Providers
 
             try
             {
-                result.Item = new T();
+                result.Item = new T
+                {
+                    IndexNumber = info.IndexNumber
+                };
 
                 Fetch(result, path, cancellationToken);
                 result.HasMetadata = true;

+ 89 - 0
MediaBrowser.XbmcMetadata/Providers/SeriesNfoSeasonProvider.cs

@@ -0,0 +1,89 @@
+using System.IO;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.IO;
+using MediaBrowser.XbmcMetadata.Parsers;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.XbmcMetadata.Providers
+{
+    /// <summary>
+    /// NFO provider for seasons based on series NFO.
+    /// </summary>
+    public class SeriesNfoSeasonProvider : BaseNfoProvider<Season>
+    {
+        private readonly ILogger<SeriesNfoSeasonProvider> _logger;
+        private readonly IConfigurationManager _config;
+        private readonly IProviderManager _providerManager;
+        private readonly IUserManager _userManager;
+        private readonly IUserDataManager _userDataManager;
+        private readonly IDirectoryService _directoryService;
+        private readonly ILibraryManager _libraryManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SeriesNfoSeasonProvider"/> class.
+        /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger{SeasonFromSeriesNfoProvider}"/> interface.</param>
+        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+        /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
+        /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+        /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
+        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+        public SeriesNfoSeasonProvider(
+            ILogger<SeriesNfoSeasonProvider> logger,
+            IFileSystem fileSystem,
+            IConfigurationManager config,
+            IProviderManager providerManager,
+            IUserManager userManager,
+            IUserDataManager userDataManager,
+            IDirectoryService directoryService,
+            ILibraryManager libraryManager)
+            : base(fileSystem)
+        {
+            _logger = logger;
+            _config = config;
+            _providerManager = providerManager;
+            _userManager = userManager;
+            _userDataManager = userDataManager;
+            _directoryService = directoryService;
+            _libraryManager = libraryManager;
+        }
+
+        /// <inheritdoc />
+        protected override void Fetch(MetadataResult<Season> result, string path, CancellationToken cancellationToken)
+        {
+            new SeriesNfoSeasonParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService)
+        {
+            var seasonPath = info.Path;
+            if (seasonPath is not null)
+            {
+                var path = Path.Combine(seasonPath, "tvshow.nfo");
+                if (Path.Exists(path))
+                {
+                    return directoryService.GetFile(path);
+                }
+            }
+
+            var seriesPath = _libraryManager.GetItemById(info.ParentId)?.Path;
+            if (seriesPath is not null)
+            {
+                var path = Path.Combine(seriesPath, "tvshow.nfo");
+                if (Path.Exists(path))
+                {
+                    return directoryService.GetFile(path);
+                }
+            }
+
+            return null;
+        }
+    }
+}