Explorar o código

Fix: parsing of xbmc style multi episode nfo files (#12268)

TheMelmacian hai 10 meses
pai
achega
d4eeafe53f

+ 1 - 0
CONTRIBUTORS.md

@@ -185,6 +185,7 @@
  - [Vedant](https://github.com/viktory36/)
  - [NotSaifA](https://github.com/NotSaifA)
  - [HonestlyWhoKnows](https://github.com/honestlywhoknows)
+ - [TheMelmacian](https://github.com/TheMelmacian)
  - [ItsAllAboutTheCode](https://github.com/ItsAllAboutTheCode)
 
 # Emby Contributors

+ 57 - 59
MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs

@@ -59,80 +59,50 @@ namespace MediaBrowser.XbmcMetadata.Parsers
             try
             {
                 // Extract episode details from the first episodedetails block
-                using (var stringReader = new StringReader(xml))
-                using (var reader = XmlReader.Create(stringReader, settings))
-                {
-                    reader.MoveToContent();
-                    reader.Read();
-
-                    // Loop through each element
-                    while (!reader.EOF && reader.ReadState == ReadState.Interactive)
-                    {
-                        cancellationToken.ThrowIfCancellationRequested();
-
-                        if (reader.NodeType == XmlNodeType.Element)
-                        {
-                            FetchDataFromXmlNode(reader, item);
-                        }
-                        else
-                        {
-                            reader.Read();
-                        }
-                    }
-                }
+                ReadEpisodeDetailsFromXml(item, xml, settings, cancellationToken);
 
                 // Extract the last episode number from nfo
-                // Retrieves all title and plot tags from the rest of the nfo and concatenates them with the first episode
+                // Retrieves all additional episodedetails blocks from the rest of the nfo and concatenates the name, originalTitle and overview tags with the first episode
                 // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag
                 var name = new StringBuilder(item.Item.Name);
+                var originalTitle = new StringBuilder(item.Item.OriginalTitle);
                 var overview = new StringBuilder(item.Item.Overview);
                 while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1)
                 {
                     xml = xmlFile.Substring(0, index + srch.Length);
                     xmlFile = xmlFile.Substring(index + srch.Length);
 
-                    using (var stringReader = new StringReader(xml))
-                    using (var reader = XmlReader.Create(stringReader, settings))
+                    var additionalEpisode = new MetadataResult<Episode>()
+                    {
+                        Item = new Episode()
+                    };
+
+                    // Extract episode details from additional episodedetails block
+                    ReadEpisodeDetailsFromXml(additionalEpisode, xml, settings, cancellationToken);
+
+                    if (!string.IsNullOrEmpty(additionalEpisode.Item.Name))
+                    {
+                        name.Append(" / ").Append(additionalEpisode.Item.Name);
+                    }
+
+                    if (!string.IsNullOrEmpty(additionalEpisode.Item.Overview))
+                    {
+                        overview.Append(" / ").Append(additionalEpisode.Item.Overview);
+                    }
+
+                    if (!string.IsNullOrEmpty(additionalEpisode.Item.OriginalTitle))
+                    {
+                        originalTitle.Append(" / ").Append(additionalEpisode.Item.OriginalTitle);
+                    }
+
+                    if (additionalEpisode.Item.IndexNumber != null)
                     {
-                        reader.MoveToContent();
-
-                        while (!reader.EOF && reader.ReadState == ReadState.Interactive)
-                        {
-                            cancellationToken.ThrowIfCancellationRequested();
-
-                            if (reader.NodeType == XmlNodeType.Element)
-                            {
-                                switch (reader.Name)
-                                {
-                                    case "name":
-                                    case "title":
-                                    case "localtitle":
-                                        name.Append(" / ").Append(reader.ReadElementContentAsString());
-                                        break;
-                                    case "episode":
-                                        {
-                                            if (int.TryParse(reader.ReadElementContentAsString(), out var num))
-                                            {
-                                                item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
-                                            }
-
-                                            break;
-                                        }
-
-                                    case "biography":
-                                    case "plot":
-                                    case "review":
-                                        overview.Append(" / ").Append(reader.ReadElementContentAsString());
-                                        break;
-                                }
-                            }
-
-                            reader.Read();
-                        }
+                        item.Item.IndexNumberEnd = Math.Max((int)additionalEpisode.Item.IndexNumber, item.Item.IndexNumberEnd ?? (int)additionalEpisode.Item.IndexNumber);
                     }
                 }
 
                 item.Item.Name = name.ToString();
+                item.Item.OriginalTitle = originalTitle.ToString();
                 item.Item.Overview = overview.ToString();
             }
             catch (XmlException)
@@ -200,5 +170,33 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                     break;
             }
         }
+
+        /// <summary>
+        /// Reads the episode details from the given xml and saves the result in the provided result item.
+        /// </summary>
+        private void ReadEpisodeDetailsFromXml(MetadataResult<Episode> item, string xml, XmlReaderSettings settings, CancellationToken cancellationToken)
+        {
+            using (var stringReader = new StringReader(xml))
+            using (var reader = XmlReader.Create(stringReader, settings))
+            {
+                reader.MoveToContent();
+                reader.Read();
+
+                // Loop through each element
+                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+                {
+                    cancellationToken.ThrowIfCancellationRequested();
+
+                    if (reader.NodeType == XmlNodeType.Element)
+                    {
+                        FetchDataFromXmlNode(reader, item);
+                    }
+                    else
+                    {
+                        reader.Read();
+                    }
+                }
+            }
+        }
     }
 }

+ 24 - 0
tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs

@@ -123,6 +123,30 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
             Assert.Equal(2004, item.ProductionYear);
         }
 
+        [Fact]
+        public void Fetch_Valid_MultiEpisode_With_Missing_Tags_Success()
+        {
+            var result = new MetadataResult<Episode>()
+            {
+                Item = new Episode()
+            };
+
+            _parser.Fetch(result, "Test Data/Stargate Atlantis S01E01-E04.nfo", CancellationToken.None);
+
+            var item = result.Item;
+            // <title> provided for episode 1, 3 and 4
+            Assert.Equal("Rising / Hide and Seek / Thirty-Eight Minutes", item.Name);
+            // <originaltitle> provided for all episodes
+            Assert.Equal("Rising (1) / Rising (2) / Hide and Seek / Thirty-Eight Minutes", item.OriginalTitle);
+            Assert.Equal(1, item.IndexNumber);
+            Assert.Equal(4, item.IndexNumberEnd);
+            Assert.Equal(1, item.ParentIndexNumber);
+            // <plot> only provided for episode 1
+            Assert.Equal("A new Stargate team embarks on a dangerous mission to a distant galaxy, where they discover a mythical lost city -- and a deadly new enemy.", item.Overview);
+            Assert.Equal(new DateTime(2004, 7, 16), item.PremiereDate);
+            Assert.Equal(2004, item.ProductionYear);
+        }
+
         [Fact]
         public void Parse_GivenFileWithThumbWithoutAspect_Success()
         {

+ 24 - 0
tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo

@@ -7,6 +7,18 @@
   <thumb>https://artworks.thetvdb.com/banners/episodes/70851/25333.jpg</thumb>
   <watched>false</watched>
   <rating>8.0</rating>
+  <actor>
+    <name>Joe Flanigan</name>
+    <role>John Sheppard</role>
+    <order>0</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/5AA1ORKIsnMakT6fCVy3JKlzMs6.jpg</thumb>
+  </actor>
+  <actor>
+    <name>David Hewlett</name>
+    <role>Rodney McKay</role>
+    <order>1</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/hUcYyssAPCqnZ4GjolhOWXHTWSa.jpg</thumb>
+  </actor>
 </episodedetails>
 <episodedetails>
   <title>Rising (2)</title>
@@ -17,4 +29,16 @@
   <thumb>https://artworks.thetvdb.com/banners/episodes/70851/25334.jpg</thumb>
   <watched>false</watched>
   <rating>7.9</rating>
+  <actor>
+    <name>Joe Flanigan</name>
+    <role>John Sheppard</role>
+    <order>0</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/5AA1ORKIsnMakT6fCVy3JKlzMs6.jpg</thumb>
+  </actor>
+  <actor>
+    <name>David Hewlett</name>
+    <role>Rodney McKay</role>
+    <order>1</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/hUcYyssAPCqnZ4GjolhOWXHTWSa.jpg</thumb>
+  </actor>
 </episodedetails>

+ 89 - 0
tests/Jellyfin.XbmcMetadata.Tests/Test Data/Stargate Atlantis S01E01-E04.nfo

@@ -0,0 +1,89 @@
+<episodedetails>
+  <title>Rising</title>
+  <originaltitle>Rising (1)</originaltitle>
+  <season>1</season>
+  <episode>1</episode>
+  <aired>2004-07-16</aired>
+  <plot>A new Stargate team embarks on a dangerous mission to a distant galaxy, where they discover a mythical lost city -- and a deadly new enemy.</plot>
+  <thumb>https://artworks.thetvdb.com/banners/episodes/70851/25333.jpg</thumb>
+  <watched>false</watched>
+  <rating>8.0</rating>
+  <actor>
+    <name>Joe Flanigan</name>
+    <role>John Sheppard</role>
+    <order>0</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/5AA1ORKIsnMakT6fCVy3JKlzMs6.jpg</thumb>
+  </actor>
+  <actor>
+    <name>David Hewlett</name>
+    <role>Rodney McKay</role>
+    <order>1</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/hUcYyssAPCqnZ4GjolhOWXHTWSa.jpg</thumb>
+  </actor>
+</episodedetails>
+<episodedetails>
+  <originaltitle>Rising (2)</originaltitle>
+  <season>1</season>
+  <episode>2</episode>
+  <aired>2004-07-16</aired>
+  <thumb>https://artworks.thetvdb.com/banners/episodes/70851/25334.jpg</thumb>
+  <watched>false</watched>
+  <rating>7.9</rating>
+  <actor>
+    <name>Joe Flanigan</name>
+    <role>John Sheppard</role>
+    <order>0</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/5AA1ORKIsnMakT6fCVy3JKlzMs6.jpg</thumb>
+  </actor>
+  <actor>
+    <name>David Hewlett</name>
+    <role>Rodney McKay</role>
+    <order>1</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/hUcYyssAPCqnZ4GjolhOWXHTWSa.jpg</thumb>
+  </actor>
+</episodedetails>
+<episodedetails>
+  <title>Hide and Seek</title>
+  <originaltitle>Hide and Seek</originaltitle>
+  <season>1</season>
+  <episode>3</episode>
+  <aired>2004-07-23</aired>
+  <thumb>https://artworks.thetvdb.com/banners/episodes/70851/25335.jpg</thumb>
+  <watched>false</watched>
+  <rating>7.5</rating>
+  <actor>
+    <name>Joe Flanigan</name>
+    <role>John Sheppard</role>
+    <order>0</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/5AA1ORKIsnMakT6fCVy3JKlzMs6.jpg</thumb>
+  </actor>
+  <actor>
+    <name>David Hewlett</name>
+    <role>Rodney McKay</role>
+    <order>1</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/hUcYyssAPCqnZ4GjolhOWXHTWSa.jpg</thumb>
+  </actor>
+</episodedetails>
+<episodedetails>
+  <title>Thirty-Eight Minutes</title>
+  <originaltitle>Thirty-Eight Minutes</originaltitle>
+  <season>1</season>
+  <episode>4</episode>
+  <aired>2004-07-23</aired>
+  <thumb>https://artworks.thetvdb.com/banners/episodes/70851/25336.jpg</thumb>
+  <watched>false</watched>
+  <rating>7.5</rating>
+  <actor>
+    <name>Joe Flanigan</name>
+    <role>John Sheppard</role>
+    <order>0</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/5AA1ORKIsnMakT6fCVy3JKlzMs6.jpg</thumb>
+  </actor>
+  <actor>
+    <name>David Hewlett</name>
+    <role>Rodney McKay</role>
+    <order>1</order>
+    <thumb>https://image.tmdb.org/t/p/w300_and_h450_bestv2/hUcYyssAPCqnZ4GjolhOWXHTWSa.jpg</thumb>
+  </actor>
+</episodedetails>
+