diff options
| author | TheMelmacian <76712303+TheMelmacian@users.noreply.github.com> | 2024-07-30 17:51:08 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-30 09:51:08 -0600 |
| commit | d4eeafe53ff8ae2f287f5dca49a873cd71f4c3da (patch) | |
| tree | be58617b2fb70f3f6642cbe70b0b11055dd6e3c2 | |
| parent | 0a1a109b2e9503213debdc8445910cb4c93ae382 (diff) | |
Fix: parsing of xbmc style multi episode nfo files (#12268)
5 files changed, 195 insertions, 59 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index edbc846d6..7c2e72327 100644 --- a/CONTRIBUTORS.md +++ b/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 diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index 044efb51e..2a1a14834 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/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(); + } + } + } + } } } diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs index c0d06116b..3721d1f7a 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs @@ -124,6 +124,30 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers } [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() { var result = new MetadataResult<Episode> diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo index 56250c09a..e95f5002a 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo +++ b/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> diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Stargate Atlantis S01E01-E04.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Stargate Atlantis S01E01-E04.nfo new file mode 100644 index 000000000..7dee0110c --- /dev/null +++ b/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> + |
