Преглед изворни кода

Merge pull request #8203 from Shadowghost/nfo-season-names

Implement NFO named season parsing
Cody Robibero пре 2 година
родитељ
комит
81cf798430

+ 18 - 8
Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs

@@ -81,14 +81,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                 if (season.IndexNumber.HasValue)
                 {
                     var seasonNumber = season.IndexNumber.Value;
-
-                    season.Name = seasonNumber == 0 ?
-                        args.LibraryOptions.SeasonZeroDisplayName :
-                        string.Format(
-                            CultureInfo.InvariantCulture,
-                            _localization.GetLocalizedString("NameSeasonNumber"),
-                            seasonNumber,
-                            args.LibraryOptions.PreferredMetadataLanguage);
+                    if (string.IsNullOrEmpty(season.Name))
+                    {
+                        var seasonNames = series.SeasonNames;
+                        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);
+                        }
+                    }
                 }
 
                 return season;

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

@@ -28,12 +28,16 @@ namespace MediaBrowser.Controller.Entities.TV
         public Series()
         {
             AirDays = Array.Empty<DayOfWeek>();
+            SeasonNames = new Dictionary<int, string>();
         }
 
         public DayOfWeek[] AirDays { get; set; }
 
         public string AirTime { get; set; }
 
+        [JsonIgnore]
+        public Dictionary<int, string> SeasonNames { get; set; }
+
         [JsonIgnore]
         public override bool SupportsAddingToPlaylist => true;
 

+ 1 - 0
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -12,6 +12,7 @@ using Jellyfin.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;

+ 53 - 21
MediaBrowser.Providers/TV/SeriesMetadataService.cs

@@ -41,7 +41,7 @@ namespace MediaBrowser.Providers.TV
 
             RemoveObsoleteEpisodes(item);
             RemoveObsoleteSeasons(item);
-            await FillInMissingSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
+            await UpdateAndCreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
         }
 
         /// <inheritdoc />
@@ -67,6 +67,20 @@ namespace MediaBrowser.Providers.TV
 
             var sourceItem = source.Item;
             var targetItem = target.Item;
+            var sourceSeasonNames = sourceItem.SeasonNames;
+            var targetSeasonNames = targetItem.SeasonNames;
+
+            if (replaceData || targetSeasonNames.Count == 0)
+            {
+                targetItem.SeasonNames = sourceSeasonNames;
+            }
+            else if (targetSeasonNames.Count != sourceSeasonNames.Count || !sourceSeasonNames.Keys.All(targetSeasonNames.ContainsKey))
+            {
+                foreach (var (number, name) in sourceSeasonNames)
+                {
+                    targetSeasonNames.TryAdd(number, name);
+                }
+            }
 
             if (replaceData || string.IsNullOrEmpty(targetItem.AirTime))
             {
@@ -86,7 +100,7 @@ namespace MediaBrowser.Providers.TV
 
         private void RemoveObsoleteSeasons(Series series)
         {
-            // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in FillInMissingSeasonsAsync.
+            // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in UpdateAndCreateSeasonsAsync.
             var physicalSeasonNumbers = new HashSet<int>();
             var virtualSeasons = new List<Season>();
             foreach (var existingSeason in series.Children.OfType<Season>())
@@ -177,36 +191,43 @@ namespace MediaBrowser.Providers.TV
         }
 
         /// <summary>
-        /// Creates seasons for all episodes that aren't in a season folder.
+        /// 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 FillInMissingSeasonsAsync(Series series, CancellationToken cancellationToken)
+        private async Task UpdateAndCreateSeasonsAsync(Series series, CancellationToken cancellationToken)
         {
+            var seasonNames = series.SeasonNames;
             var seriesChildren = series.GetRecursiveChildren(i => i is Episode || i is Season);
-            var episodesInSeriesFolder = seriesChildren
+            var seasons = seriesChildren.OfType<Season>().ToList();
+            var uniqueSeasonNumbers = seriesChildren
                 .OfType<Episode>()
-                .Where(i => !i.IsInSeasonFolder);
-
-            List<Season> seasons = seriesChildren.OfType<Season>().ToList();
+                .Select(e => e.ParentIndexNumber >= 0 ? e.ParentIndexNumber : null)
+                .Distinct();
 
             // Loop through the unique season numbers
-            foreach (var episode in episodesInSeriesFolder)
+            foreach (var seasonNumber in uniqueSeasonNumbers)
             {
                 // Null season numbers will have a 'dummy' season created because seasons are always required.
-                var seasonNumber = episode.ParentIndexNumber >= 0 ? episode.ParentIndexNumber : null;
                 var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber);
+                string? seasonName = null;
+
+                if (seasonNumber.HasValue && seasonNames.TryGetValue(seasonNumber.Value, out var tmp))
+                {
+                    seasonName = tmp;
+                }
 
                 if (existingSeason is null)
                 {
-                    var season = await CreateSeasonAsync(series, seasonNumber, cancellationToken).ConfigureAwait(false);
-                    seasons.Add(season);
+                    var season = await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false);
+                    series.AddChild(season);
                 }
-                else if (existingSeason.IsVirtualItem)
+                else
                 {
-                    existingSeason.IsVirtualItem = false;
+                    existingSeason.Name = GetValidSeasonNameForSeries(series, seasonName, seasonNumber);
                     await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
                 }
             }
@@ -216,21 +237,17 @@ namespace MediaBrowser.Providers.TV
         /// Creates a new season, adds it to the database by linking it to the [series] and refreshes the metadata.
         /// </summary>
         /// <param name="series">The series.</param>
+        /// <param name="seasonName">The season name.</param>
         /// <param name="seasonNumber">The season number.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>The newly created season.</returns>
         private async Task<Season> CreateSeasonAsync(
             Series series,
+            string? seasonName,
             int? seasonNumber,
             CancellationToken cancellationToken)
         {
-            string seasonName = seasonNumber switch
-            {
-                null => _localizationManager.GetLocalizedString("NameSeasonUnknown"),
-                0 => LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName,
-                _ => string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value)
-            };
-
+            seasonName = GetValidSeasonNameForSeries(series, seasonName, seasonNumber);
             Logger.LogInformation("Creating Season {SeasonName} entry for {SeriesName}", seasonName, series.Name);
 
             var season = new Season
@@ -251,5 +268,20 @@ namespace MediaBrowser.Providers.TV
 
             return season;
         }
+
+        private string GetValidSeasonNameForSeries(Series series, string? seasonName, int? seasonNumber)
+        {
+            if (string.IsNullOrEmpty(seasonName))
+            {
+                seasonName = seasonNumber switch
+                {
+                    null => _localizationManager.GetLocalizedString("NameSeasonUnknown"),
+                    0 => LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName,
+                    _ => string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value)
+                };
+            }
+
+            return seasonName;
+        }
     }
 }

+ 12 - 0
MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs

@@ -55,6 +55,18 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                         break;
                     }
 
+                case "seasonname":
+                    {
+                        var name = reader.ReadElementContentAsString();
+
+                        if (!string.IsNullOrWhiteSpace(name))
+                        {
+                            item.Name = name;
+                        }
+
+                        break;
+                    }
+
                 default:
                     base.FetchDataFromXmlNode(reader, itemResult);
                     break;

+ 15 - 0
MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs

@@ -1,4 +1,6 @@
 using System;
+using System.Collections.Generic;
+using System.Globalization;
 using System.Xml;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Entities.TV;
@@ -110,6 +112,19 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                         break;
                     }
 
+                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.SeasonNames[seasonNumber] = name;
+                        }
+
+                        break;
+                    }
+
                 default:
                     base.FetchDataFromXmlNode(reader, itemResult);
                     break;