aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheMelmacian <76712303+TheMelmacian@users.noreply.github.com>2024-07-30 17:51:08 +0200
committerGitHub <noreply@github.com>2024-07-30 09:51:08 -0600
commitd4eeafe53ff8ae2f287f5dca49a873cd71f4c3da (patch)
treebe58617b2fb70f3f6642cbe70b0b11055dd6e3c2
parent0a1a109b2e9503213debdc8445910cb4c93ae382 (diff)
Fix: parsing of xbmc style multi episode nfo files (#12268)
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs116
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs24
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo24
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Stargate Atlantis S01E01-E04.nfo89
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>
+