aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj6
-rw-r--r--tests/Jellyfin.Api.Tests/ParseNetworkTests.cs88
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj2
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs16
-rw-r--r--tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs16
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj2
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs96
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs38
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs35
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ass22
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/example.srt8
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj2
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj4
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs12
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj4
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs1
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj40
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs103
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs107
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs72
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs70
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs83
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs91
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/American Gods.nfo185
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo230
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Season 01.nfo86
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Best of 1980-1990.nfo29
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo111
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/U2.nfo70
31 files changed, 1597 insertions, 36 deletions
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 45c93987b..98a2cda60 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -16,13 +16,13 @@
<PackageReference Include="AutoFixture" Version="4.15.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="1.3.0" />
- <PackageReference Include="Moq" Version="4.15.2" />
+ <PackageReference Include="coverlet.collector" Version="3.0.1" />
+ <PackageReference Include="Moq" Version="4.16.0" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs
new file mode 100644
index 000000000..6c3fd0ee1
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Globalization;
+using System.Text;
+using Jellyfin.Networking.Configuration;
+using Jellyfin.Networking.Manager;
+using Jellyfin.Server.Extensions;
+using MediaBrowser.Common.Configuration;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Api.Tests
+{
+ public class ParseNetworkTests
+ {
+ /// <summary>
+ /// Order of the result has always got to be hosts, then networks.
+ /// </summary>
+ /// <param name="ip4">IP4 enabled.</param>
+ /// <param name="ip6">IP6 enabled.</param>
+ /// <param name="hostList">List to parse.</param>
+ /// <param name="match">What it should match.</param>
+ [Theory]
+ // [InlineData(true, true, "192.168.0.0/16,www.yahoo.co.uk", "::ffff:212.82.100.150,::ffff:192.168.0.0/16")] <- fails on Max. www.yahoo.co.uk resolves to a different ip address.
+ // [InlineData(true, false, "192.168.0.0/16,www.yahoo.co.uk", "212.82.100.150,192.168.0.0/16")]
+ [InlineData(true, true, "192.168.t,127.0.0.1,1234.1232.12.1234", "::ffff:127.0.0.1")]
+ [InlineData(true, false, "192.168.x,127.0.0.1,1234.1232.12.1234", "127.0.0.1")]
+ [InlineData(true, true, "::1", "::1/128")]
+ public void TestNetworks(bool ip4, bool ip6, string hostList, string match)
+ {
+ using var nm = CreateNetworkManager();
+
+ var settings = new NetworkConfiguration
+ {
+ EnableIPV4 = ip4,
+ EnableIPV6 = ip6
+ };
+
+ var result = match + ',';
+ ForwardedHeadersOptions options = new ForwardedHeadersOptions();
+
+ // Need this here as ::1 and 127.0.0.1 are in them by default.
+ options.KnownProxies.Clear();
+ options.KnownNetworks.Clear();
+
+ ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(","), options);
+
+ var sb = new StringBuilder();
+ foreach (var item in options.KnownProxies)
+ {
+ sb.Append(item);
+ sb.Append(',');
+ }
+
+ foreach (var item in options.KnownNetworks)
+ {
+ sb.Append(item.Prefix);
+ sb.Append('/');
+ sb.Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture));
+ sb.Append(',');
+ }
+
+ Assert.Equal(sb.ToString(), result);
+ }
+
+ private static IConfigurationManager GetMockConfig(NetworkConfiguration conf)
+ {
+ var configManager = new Mock<IConfigurationManager>
+ {
+ CallBase = true
+ };
+ configManager.Setup(x => x.GetConfiguration(It.IsAny<string>())).Returns(conf);
+ return configManager.Object;
+ }
+
+ private static NetworkManager CreateNetworkManager()
+ {
+ var conf = new NetworkConfiguration()
+ {
+ EnableIPV6 = true,
+ EnableIPV4 = true,
+ };
+
+ return new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 19c5612c0..7ff4d0e87 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="1.3.0" />
+ <PackageReference Include="coverlet.collector" Version="3.0.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
index efc0c4af9..22bc7afb9 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
+++ b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
@@ -39,18 +39,30 @@ namespace Jellyfin.Common.Tests.Json
}
[Fact]
- public void Deserialize_Null_EmptyGuid()
+ public void Deserialize_Null_Null()
{
Assert.Null(JsonSerializer.Deserialize<Guid?>("null", _options));
}
[Fact]
- public void Serialize_EmptyGuid_EmptyGuid()
+ public void Deserialize_EmptyGuid_EmptyGuid()
+ {
+ Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid?>(@"""00000000-0000-0000-0000-000000000000""", _options));
+ }
+
+ [Fact]
+ public void Serialize_EmptyGuid_Null()
{
Assert.Equal("null", JsonSerializer.Serialize((Guid?)Guid.Empty, _options));
}
[Fact]
+ public void Serialize_Null_Null()
+ {
+ Assert.Equal("null", JsonSerializer.Serialize((Guid?)null, _options));
+ }
+
+ [Fact]
public void Serialize_Valid_NoDash_Success()
{
var guid = (Guid?)new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
diff --git a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs
index 929bb92aa..0adf098c3 100644
--- a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs
+++ b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs
@@ -1,6 +1,5 @@
using System;
using System.Linq;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Sorting;
using Xunit;
@@ -8,8 +7,6 @@ namespace Jellyfin.Controller.Tests
{
public class AlphanumComparatorTests
{
- private readonly Random _rng = new Random(42);
-
// InlineData is pre-sorted
[Theory]
[InlineData(null, "", "1", "9", "10", "a", "z")]
@@ -25,18 +22,7 @@ namespace Jellyfin.Controller.Tests
[InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")]
public void AlphanumComparatorTest(params string?[] strings)
{
- var copy = (string?[])strings.Clone();
- if (strings.Length == 2)
- {
- var tmp = copy[0];
- copy[0] = copy[1];
- copy[1] = tmp;
- }
- else
- {
- copy.Shuffle(_rng);
- }
-
+ var copy = strings.Reverse().ToArray();
Array.Sort(copy, new AlphanumComparator());
Assert.True(strings.SequenceEqual(copy));
}
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 1ec88dada..af412eed3 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="1.3.0" />
+ <PackageReference Include="coverlet.collector" Version="3.0.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index 8c9dc4820..21389b926 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -11,7 +11,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="1.3.0" />
+ <PackageReference Include="coverlet.collector" Version="3.0.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index c934ea1c2..9a1cfcfa4 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -22,7 +22,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="1.3.0" />
+ <PackageReference Include="coverlet.collector" Version="3.0.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs
new file mode 100644
index 000000000..d11cb242c
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs
@@ -0,0 +1,96 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using MediaBrowser.MediaEncoding.Subtitles;
+using MediaBrowser.Model.MediaInfo;
+using Xunit;
+
+namespace Jellyfin.MediaEncoding.Tests
+{
+ public class SsaParserTests
+ {
+ // commonly shared invariant value between tests, assumes default format order
+ private const string InvariantDialoguePrefix = "[Events]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,";
+
+ private SsaParser parser = new SsaParser();
+
+ [Theory]
+ [InlineData("[EvEnTs]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,text", "text")] // label casing insensitivity
+ [InlineData("[Events]\n,0:00:00.00,0:00:00.01,,,,,,,labelless dialogue", "labelless dialogue")] // no "Dialogue:" label, it is optional
+ [InlineData("[Events]\nFormat: Text, Start, End, Layer, Effect, Style\nDialogue: reordered text,0:00:00.00,0:00:00.01", "reordered text")] // reordered formats
+ [InlineData(InvariantDialoguePrefix + "Cased TEXT", "Cased TEXT")] // preserve text casing
+ [InlineData(InvariantDialoguePrefix + " text ", " text ")] // do not trim text
+ [InlineData(InvariantDialoguePrefix + "text, more text", "text, more text")] // append excess dialogue values (> 10) to text
+ [InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text{\\fn} end", "start <font face=\"Font Name\">text</font> end")] // font name
+ [InlineData(InvariantDialoguePrefix + "start {\\fs10}text{\\fs} end", "start <font size=\"10\">text</font> end")] // font size
+ [InlineData(InvariantDialoguePrefix + "start {\\c&H112233}text{\\c} end", "start <font color=\"#332211\">text</font> end")] // color
+ [InlineData(InvariantDialoguePrefix + "start {\\1c&H112233}text{\\1c} end", "start <font color=\"#332211\">text</font> end")] // primay color
+ [InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text1 {\\fs10}text2{\\fs}{\\fn} {\\1c&H112233}text3{\\1c} end", "start <font face=\"Font Name\">text1 <font size=\"10\">text2</font></font> <font color=\"#332211\">text3</font> end")] // nested formatting
+ public void Parse(string ssa, string expectedText)
+ {
+ using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
+ {
+ SubtitleTrackInfo subtitleTrackInfo = parser.Parse(stream, CancellationToken.None);
+ SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[0];
+ Assert.Equal(expectedText, actual.Text);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(Parse_MultipleDialogues_TestData))]
+ public void Parse_MultipleDialogues(string ssa, IReadOnlyList<SubtitleTrackEvent> expectedSubtitleTrackEvents)
+ {
+ using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
+ {
+ SubtitleTrackInfo subtitleTrackInfo = parser.Parse(stream, CancellationToken.None);
+
+ Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);
+
+ for (int i = 0; i < expectedSubtitleTrackEvents.Count; ++i)
+ {
+ SubtitleTrackEvent expected = expectedSubtitleTrackEvents[i];
+ SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[i];
+
+ Assert.Equal(expected.StartPositionTicks, actual.StartPositionTicks);
+ Assert.Equal(expected.EndPositionTicks, actual.EndPositionTicks);
+ Assert.Equal(expected.Text, actual.Text);
+ }
+ }
+ }
+
+ public static IEnumerable<object[]> Parse_MultipleDialogues_TestData()
+ {
+ yield return new object[]
+ {
+ @"[Events]
+ Format: Layer, Start, End, Text
+ Dialogue: ,0:00:01.18,0:00:01.85,dialogue1
+ Dialogue: ,0:00:02.18,0:00:02.85,dialogue2
+ Dialogue: ,0:00:03.18,0:00:03.85,dialogue3
+ ",
+ new List<SubtitleTrackEvent>
+ {
+ new SubtitleTrackEvent
+ {
+ StartPositionTicks = 11800000,
+ EndPositionTicks = 18500000,
+ Text = "dialogue1"
+ },
+ new SubtitleTrackEvent
+ {
+ StartPositionTicks = 21800000,
+ EndPositionTicks = 28500000,
+ Text = "dialogue2"
+ },
+ new SubtitleTrackEvent
+ {
+ StartPositionTicks = 31800000,
+ EndPositionTicks = 38500000,
+ Text = "dialogue3"
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
new file mode 100644
index 000000000..14ad49839
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Threading;
+using MediaBrowser.MediaEncoding.Subtitles;
+using Xunit;
+
+namespace Jellyfin.MediaEncoding.Subtitles.Tests
+{
+ public class AssParserTests
+ {
+ [Fact]
+ public void Parse_Valid_Success()
+ {
+ using (var stream = File.OpenRead("Test Data/example.ass"))
+ {
+ var parsed = new AssParser().Parse(stream, CancellationToken.None);
+ Assert.Single(parsed.TrackEvents);
+ var trackEvent = parsed.TrackEvents[0];
+
+ Assert.Equal("1", trackEvent.Id);
+ Assert.Equal(TimeSpan.Parse("00:00:01.18", CultureInfo.InvariantCulture).Ticks, trackEvent.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:00:06.85", CultureInfo.InvariantCulture).Ticks, trackEvent.EndPositionTicks);
+ Assert.Equal("Like an Angel with pity on nobody\r\nThe second line in subtitle", trackEvent.Text);
+ }
+ }
+
+ [Fact]
+ public void ParseFieldHeaders_Valid_Success()
+ {
+ const string Line = "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text";
+ var headers = AssParser.ParseFieldHeaders(Line);
+ Assert.Equal(1, headers["Start"]);
+ Assert.Equal(2, headers["End"]);
+ Assert.Equal(9, headers["Text"]);
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
new file mode 100644
index 000000000..3e2d2de10
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Threading;
+using MediaBrowser.MediaEncoding.Subtitles;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Jellyfin.MediaEncoding.Subtitles.Tests
+{
+ public class SrtParserTests
+ {
+ [Fact]
+ public void Parse_Valid_Success()
+ {
+ using (var stream = File.OpenRead("Test Data/example.srt"))
+ {
+ var parsed = new SrtParser(new NullLogger<SrtParser>()).Parse(stream, CancellationToken.None);
+ Assert.Equal(2, parsed.TrackEvents.Count);
+
+ var trackEvent1 = parsed.TrackEvents[0];
+ Assert.Equal("1", trackEvent1.Id);
+ Assert.Equal(TimeSpan.Parse("00:02:17.440", CultureInfo.InvariantCulture).Ticks, trackEvent1.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:02:20.375", CultureInfo.InvariantCulture).Ticks, trackEvent1.EndPositionTicks);
+ Assert.Equal("Senator, we're making\r\nour final approach into Coruscant.", trackEvent1.Text);
+
+ var trackEvent2 = parsed.TrackEvents[1];
+ Assert.Equal("2", trackEvent2.Id);
+ Assert.Equal(TimeSpan.Parse("00:02:20.476", CultureInfo.InvariantCulture).Ticks, trackEvent2.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:02:22.501", CultureInfo.InvariantCulture).Ticks, trackEvent2.EndPositionTicks);
+ Assert.Equal("Very good, Lieutenant.", trackEvent2.Text);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ass b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ass
new file mode 100644
index 000000000..d5ac31d70
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ass
@@ -0,0 +1,22 @@
+[Script Info]
+; Script generated by Aegisub
+; http://www.aegisub.org
+Title: Neon Genesis Evangelion - Episode 26 (neutral Spanish)
+Original Script: RoRo
+Script Updated By: version 2.8.01
+ScriptType: v4.00+
+Collisions: Normal
+PlayResY: 600
+PlayDepth: 0
+Timer: 100,0000
+Video Aspect Ratio: 0
+Video Zoom: 6
+Video Position: 0
+
+[V4+ Styles]
+Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
+Style: DefaultVCD, Arial,28,&H00B4FCFC,&H00B4FCFC,&H00000008,&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0
+
+[Events]
+Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+Dialogue: 0,0:00:01.18,0:00:06.85,DefaultVCD, NTP,0000,0000,0000,,{\pos(400,570)}Like an Angel with pity on nobody\NThe second line in subtitle
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.srt b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.srt
new file mode 100644
index 000000000..78d74014e
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.srt
@@ -0,0 +1,8 @@
+1
+00:02:17,440 --> 00:02:20,375
+Senator, we're making
+our final approach into Coruscant.
+
+2
+00:02:20,476 --> 00:02:22,501
+Very good, Lieutenant.
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 6118581e1..31e41b12f 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="1.3.0" />
+ <PackageReference Include="coverlet.collector" Version="3.0.1" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
index 90782f6bb..bb2bae8e8 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
@@ -16,8 +16,8 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.3.0" />
- <PackageReference Include="Moq" Version="4.15.2" />
+ <PackageReference Include="coverlet.collector" Version="3.0.1" />
+ <PackageReference Include="Moq" Version="4.16.0" />
</ItemGroup>
<!-- Code Analyzers-->
diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
index c350685af..b7c1510d2 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
@@ -54,13 +54,13 @@ namespace Jellyfin.Networking.Tests
/// <summary>
/// Checks the ability to ignore interfaces
/// </summary>
- /// <param name="interfaces">Mock network setup, in the format (IP address, interface index, interface name) : .... </param>
+ /// <param name="interfaces">Mock network setup, in the format (IP address, interface index, interface name) | .... </param>
/// <param name="lan">LAN addresses.</param>
/// <param name="value">Bind addresses that are excluded.</param>
[Theory]
- [InlineData("192.168.1.208/24,-16,eth16:200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.0/24", "[192.168.1.208/24,200.200.200.200/24]")]
- [InlineData("192.168.1.208/24,-16,eth16:200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
- [InlineData("192.168.1.208/24,-16,vEthernet1:192.168.1.208/24,-16,vEthernet212;200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
+ [InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.0/24", "[192.168.1.208/24,200.200.200.200/24]")]
+ [InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
+ [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.1.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
public void IgnoreVirtualInterfaces(string interfaces, string lan, string value)
{
var conf = new NetworkConfiguration()
@@ -434,7 +434,7 @@ namespace Jellyfin.Networking.Tests
EnableIPV4 = true
};
- NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16:200.200.200.200/24,11,eth11";
+ NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11";
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
NetworkManager.MockNetworkSettings = string.Empty;
@@ -501,7 +501,7 @@ namespace Jellyfin.Networking.Tests
PublishedServerUriBySubnet = new string[] { publishedServers }
};
- NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16:200.200.200.200/24,11,eth11";
+ NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11";
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
NetworkManager.MockNetworkSettings = string.Empty;
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 80259a55f..7f909847e 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -17,10 +17,10 @@
<PackageReference Include="AutoFixture" Version="4.15.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
- <PackageReference Include="Moq" Version="4.15.2" />
+ <PackageReference Include="Moq" Version="4.16.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="1.3.0" />
+ <PackageReference Include="coverlet.collector" Version="3.0.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs
index fb7cf6a47..75939526d 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
new file mode 100644
index 000000000..fe40122a3
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -0,0 +1,40 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <None Include="Test Data\**\*.*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Moq" Version="4.16.0" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="coverlet.collector" Version="3.0.1" />
+ </ItemGroup>
+
+ <!-- Code Analyzers -->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../../MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+
+</Project>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
new file mode 100644
index 000000000..67b4b969a
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Linq;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.XbmcMetadata.Parsers;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+#pragma warning disable CA5369
+
+namespace Jellyfin.XbmcMetadata.Tests.Parsers
+{
+ public class EpisodeNfoProviderTests
+ {
+ private readonly EpisodeNfoParser _parser;
+
+ public EpisodeNfoProviderTests()
+ {
+ var providerManager = new Mock<IProviderManager>();
+ providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
+ .Returns(Enumerable.Empty<ExternalIdInfo>());
+ var config = new Mock<IConfigurationManager>();
+ config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
+ .Returns(new XbmcMetadataOptions());
+ _parser = new EpisodeNfoParser(new NullLogger<EpisodeNfoParser>(), config.Object, providerManager.Object);
+ }
+
+ [Fact]
+ public void Fetch_Valid_Succes()
+ {
+ var result = new MetadataResult<Episode>()
+ {
+ Item = new Episode()
+ };
+
+ _parser.Fetch(result, "Test Data/The Bone Orchard.nfo", CancellationToken.None);
+
+ var item = result.Item;
+ Assert.Equal("The Bone Orchard", item.Name);
+ Assert.Equal("American Gods", item.SeriesName);
+ Assert.Equal(1, item.IndexNumber);
+ Assert.Equal(1, item.ParentIndexNumber);
+ Assert.Equal("When Shadow Moon is released from prison early after the death of his wife, he meets Mr. Wednesday and is recruited as his bodyguard. Shadow discovers that this may be more than he bargained for.", item.Overview);
+ Assert.Equal(0, item.RunTimeTicks);
+ Assert.Equal("16", item.OfficialRating);
+ Assert.Contains("Drama", item.Genres);
+ Assert.Contains("Mystery", item.Genres);
+ Assert.Contains("Sci-Fi & Fantasy", item.Genres);
+ Assert.Equal(new DateTime(2017, 4, 30), item.PremiereDate);
+ Assert.Equal(2017, item.ProductionYear);
+ Assert.Single(item.Studios);
+ Assert.Contains("Starz", item.Studios);
+
+ // Credits
+ var writers = result.People.Where(x => x.Type == PersonType.Writer).ToArray();
+ Assert.Equal(2, writers.Length);
+ Assert.Contains("Bryan Fuller", writers.Select(x => x.Name));
+ Assert.Contains("Michael Green", writers.Select(x => x.Name));
+
+ // Direcotrs
+ var directors = result.People.Where(x => x.Type == PersonType.Director).ToArray();
+ Assert.Single(directors);
+ Assert.Contains("David Slade", directors.Select(x => x.Name));
+
+ // Actors
+ var actors = result.People.Where(x => x.Type == PersonType.Actor).ToArray();
+ Assert.Equal(11, actors.Length);
+ // Only test one actor
+ var shadow = actors.FirstOrDefault(x => x.Role.Equals("Shadow Moon", StringComparison.Ordinal));
+ Assert.NotNull(shadow);
+ Assert.Equal("Ricky Whittle", shadow!.Name);
+ Assert.Equal(0, shadow!.SortOrder);
+ Assert.Equal("http://image.tmdb.org/t/p/original/cjeDbVfBp6Qvb3C74Dfy7BKDTQN.jpg", shadow!.ImageUrl);
+
+ Assert.Equal(new DateTime(2017, 10, 7, 14, 25, 47), item.DateCreated);
+ }
+
+ [Fact]
+ public void Fetch_WithNullItem_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<Episode>();
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, "Test Data/The Bone Orchard.nfo", CancellationToken.None));
+ }
+
+ [Fact]
+ public void Fetch_NullResult_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<Episode>()
+ {
+ Item = new Episode()
+ };
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, string.Empty, CancellationToken.None));
+ }
+ }
+}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
new file mode 100644
index 000000000..e1f50876f
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Linq;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.XbmcMetadata.Parsers;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.XbmcMetadata.Tests.Parsers
+{
+ public class MovieNfoParserTests
+ {
+ private readonly MovieNfoParser _parser;
+
+ public MovieNfoParserTests()
+ {
+ var providerManager = new Mock<IProviderManager>();
+ providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
+ .Returns(Enumerable.Empty<ExternalIdInfo>());
+ var config = new Mock<IConfigurationManager>();
+ config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
+ .Returns(new XbmcMetadataOptions());
+ _parser = new MovieNfoParser(new NullLogger<MovieNfoParser>(), config.Object, providerManager.Object);
+ }
+
+ [Fact]
+ public void Fetch_Valid_Succes()
+ {
+ var result = new MetadataResult<Video>()
+ {
+ Item = new Video()
+ };
+
+ _parser.Fetch(result, "Test Data/Justice League.nfo", CancellationToken.None);
+ var item = result.Item;
+
+ Assert.Equal("Justice League", item.OriginalTitle);
+ Assert.Equal("Justice for all.", item.Tagline);
+ Assert.Equal("tt0974015", item.ProviderIds["imdb"]);
+
+ Assert.Equal(4, item.Genres.Length);
+ Assert.Contains("Action", item.Genres);
+ Assert.Contains("Adventure", item.Genres);
+ Assert.Contains("Fantasy", item.Genres);
+ Assert.Contains("Sci-Fi", item.Genres);
+
+ Assert.Equal(new DateTime(2017, 11, 15), item.PremiereDate);
+ Assert.Single(item.Studios);
+ Assert.Contains("DC Comics", item.Studios);
+
+ Assert.Equal("1.777778", item.AspectRatio);
+ Assert.Equal(1920, item.Width);
+ Assert.Equal(1080, item.Height);
+ Assert.Equal(new TimeSpan(0, 0, 6268).Ticks, item.RunTimeTicks);
+ Assert.True(item.HasSubtitles);
+
+ Assert.Equal(18, result.People.Count);
+
+ var writers = result.People.Where(x => x.Type == PersonType.Writer).ToArray();
+ Assert.Equal(2, writers.Length);
+ var writerNames = writers.Select(x => x.Name);
+ Assert.Contains("Jerry Siegel", writerNames);
+ Assert.Contains("Joe Shuster", writerNames);
+
+ var directors = result.People.Where(x => x.Type == PersonType.Director).ToArray();
+ Assert.Single(directors);
+ Assert.Equal("Zack Snyder", directors[0].Name);
+
+ var actors = result.People.Where(x => x.Type == PersonType.Actor).ToArray();
+ Assert.Equal(15, actors.Length);
+
+ // Only test one actor
+ var aquaman = actors.FirstOrDefault(x => x.Role.Equals("Aquaman", StringComparison.Ordinal));
+ Assert.NotNull(aquaman);
+ Assert.Equal("Jason Momoa", aquaman!.Name);
+ Assert.Equal(5, aquaman!.SortOrder);
+ Assert.Equal("https://m.media-amazon.com/images/M/MV5BMTI5MTU5NjM1MV5BMl5BanBnXkFtZTcwODc4MDk0Mw@@._V1_SX1024_SY1024_.jpg", aquaman!.ImageUrl);
+
+ Assert.Equal(new DateTime(2019, 8, 6, 9, 1, 18), item.DateCreated);
+ }
+
+ [Fact]
+ public void Fetch_WithNullItem_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<Video>();
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, "Test Data/Justice League.nfo", CancellationToken.None));
+ }
+
+ [Fact]
+ public void Fetch_NullResult_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<Video>()
+ {
+ Item = new Video()
+ };
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, string.Empty, CancellationToken.None));
+ }
+ }
+}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
new file mode 100644
index 000000000..bdffea560
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
@@ -0,0 +1,72 @@
+#pragma warning disable CA5369
+
+using System;
+using System.Linq;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.XbmcMetadata.Parsers;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.XbmcMetadata.Tests.Parsers
+{
+ public class MusicAlbumNfoProviderTests
+ {
+ private readonly BaseNfoParser<MusicAlbum> _parser;
+
+ public MusicAlbumNfoProviderTests()
+ {
+ var providerManager = new Mock<IProviderManager>();
+ providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
+ .Returns(Enumerable.Empty<ExternalIdInfo>());
+ var config = new Mock<IConfigurationManager>();
+ config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
+ .Returns(new XbmcMetadataOptions());
+ _parser = new BaseNfoParser<MusicAlbum>(new NullLogger<BaseNfoParser<MusicAlbum>>(), config.Object, providerManager.Object);
+ }
+
+ [Fact]
+ public void Fetch_Valid_Succes()
+ {
+ var result = new MetadataResult<MusicAlbum>()
+ {
+ Item = new MusicAlbum()
+ };
+
+ _parser.Fetch(result, "Test Data/The Best of 1980-1990.nfo", CancellationToken.None);
+ var item = result.Item;
+
+ Assert.Equal("The Best of 1980-1990", item.Name);
+ Assert.Equal(1989, item.ProductionYear);
+ Assert.Contains("Pop", item.Genres);
+ Assert.Single(item.Genres);
+ Assert.Contains("Rock/Pop", item.Tags);
+ Assert.Equal("The Best of 1980-1990 is the first greatest hits compilation by Irish rock band U2, released in November 1998. It mostly contains the group's hit singles from the eighties but also mixes in some live staples as well as one new recording, Sweetest Thing. In April 1999, a companion video (featuring music videos and live footage) was released. The album was followed by another compilation, The Best of 1990-2000, in 2002.\nA limited edition version containing a special B-sides disc was released on the same date as the single-disc version. At the time of release, the official word was that the 2-disc album would be available the first week the album went on sale, then pulled from the stores. While this threat never materialized, it did result in the 2-disc version being in very high demand. Both versions charted in the Billboard 200.\nThe boy on the cover is Peter Rowan, brother of Bono's friend Guggi (real name Derek Rowan) of the Virgin Prunes. He also appears on the covers of the early EP Three, two of the band's first three albums (Boy and War), and Early Demos.", item.Overview);
+ }
+
+ [Fact]
+ public void Fetch_WithNullItem_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<MusicAlbum>();
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, "Test Data/The Best of 1980-1990.nfo", CancellationToken.None));
+ }
+
+ [Fact]
+ public void Fetch_NullResult_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<MusicAlbum>()
+ {
+ Item = new MusicAlbum()
+ };
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, string.Empty, CancellationToken.None));
+ }
+ }
+}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
new file mode 100644
index 000000000..2a4d376c6
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Linq;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.XbmcMetadata.Parsers;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.XbmcMetadata.Tests.Parsers
+{
+ public class MusicArtistNfoParserTests
+ {
+ private readonly BaseNfoParser<MusicArtist> _parser;
+
+ public MusicArtistNfoParserTests()
+ {
+ var providerManager = new Mock<IProviderManager>();
+ providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
+ .Returns(Enumerable.Empty<ExternalIdInfo>());
+ var config = new Mock<IConfigurationManager>();
+ config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
+ .Returns(new XbmcMetadataOptions());
+ _parser = new BaseNfoParser<MusicArtist>(new NullLogger<BaseNfoParser<MusicArtist>>(), config.Object, providerManager.Object);
+ }
+
+ [Fact]
+ public void Fetch_Valid_Succes()
+ {
+ var result = new MetadataResult<MusicArtist>()
+ {
+ Item = new MusicArtist()
+ };
+
+ _parser.Fetch(result, "Test Data/U2.nfo", CancellationToken.None);
+ var item = result.Item;
+
+ Assert.Equal("U2", item.Name);
+ Assert.Equal("U2", item.SortName);
+ Assert.Equal("a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432", item.ProviderIds[MetadataProvider.MusicBrainzArtist.ToString()]);
+
+ Assert.Single(item.Genres);
+ Assert.Equal("Rock", item.Genres[0]);
+ }
+
+ [Fact]
+ public void Fetch_WithNullItem_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<MusicArtist>();
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, "Test Data/U2.nfo", CancellationToken.None));
+ }
+
+ [Fact]
+ public void Fetch_NullResult_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<MusicArtist>()
+ {
+ Item = new MusicArtist()
+ };
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, string.Empty, CancellationToken.None));
+ }
+ }
+}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
new file mode 100644
index 000000000..68b7239d2
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
@@ -0,0 +1,83 @@
+#pragma warning disable CA5369
+
+using System;
+using System.Linq;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.XbmcMetadata.Parsers;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.XbmcMetadata.Tests.Parsers
+{
+ public class SeasonNfoProviderTests
+ {
+ private readonly SeasonNfoParser _parser;
+
+ public SeasonNfoProviderTests()
+ {
+ var providerManager = new Mock<IProviderManager>();
+ providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
+ .Returns(Enumerable.Empty<ExternalIdInfo>());
+ var config = new Mock<IConfigurationManager>();
+ config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
+ .Returns(new XbmcMetadataOptions());
+ _parser = new SeasonNfoParser(new NullLogger<SeasonNfoParser>(), config.Object, providerManager.Object);
+ }
+
+ [Fact]
+ public void Fetch_Valid_Succes()
+ {
+ var result = new MetadataResult<Season>()
+ {
+ Item = new Season()
+ };
+
+ _parser.Fetch(result, "Test Data/Season 01.nfo", CancellationToken.None);
+ var item = result.Item;
+
+ Assert.Equal("Season 1", item.Name);
+ Assert.Equal(1, item.IndexNumber);
+ Assert.False(item.IsLocked);
+ Assert.Equal(2019, item.ProductionYear);
+ Assert.Equal(new DateTime(2019, 11, 08), item.PremiereDate);
+ Assert.Equal(new DateTime(2020, 06, 14, 17, 26, 51), item.DateCreated);
+
+ Assert.Equal(10, result.People.Count);
+
+ Assert.True(result.People.All(x => x.Type == PersonType.Actor));
+
+ // Only test one actor
+ var nini = result.People.FirstOrDefault(x => x.Role.Equals("Nini", StringComparison.Ordinal));
+ Assert.NotNull(nini);
+ Assert.Equal("Olivia Rodrigo", nini!.Name);
+ Assert.Equal(0, nini!.SortOrder);
+ Assert.Equal("/config/metadata/People/O/Olivia Rodrigo/poster.jpg", nini!.ImageUrl);
+ }
+
+ [Fact]
+ public void Fetch_WithNullItem_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<Season>();
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, "Test Data/Season 01.nfo", CancellationToken.None));
+ }
+
+ [Fact]
+ public void Fetch_NullResult_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<Season>()
+ {
+ Item = new Season()
+ };
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, string.Empty, CancellationToken.None));
+ }
+ }
+}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
new file mode 100644
index 000000000..3bbfb66e3
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Linq;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.XbmcMetadata.Parsers;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.XbmcMetadata.Tests.Parsers
+{
+ public class SeriesNfoParserTests
+ {
+ private readonly SeriesNfoParser _parser;
+
+ public SeriesNfoParserTests()
+ {
+ var providerManager = new Mock<IProviderManager>();
+ providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
+ .Returns(Enumerable.Empty<ExternalIdInfo>());
+ var config = new Mock<IConfigurationManager>();
+ config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
+ .Returns(new XbmcMetadataOptions());
+ _parser = new SeriesNfoParser(new NullLogger<SeriesNfoParser>(), config.Object, providerManager.Object);
+ }
+
+ [Fact]
+ public void Fetch_Valid_Succes()
+ {
+ var result = new MetadataResult<Series>()
+ {
+ Item = new Series()
+ };
+
+ _parser.Fetch(result, "Test Data/American Gods.nfo", CancellationToken.None);
+ var item = result.Item;
+
+ Assert.Equal("American Gods", item.OriginalTitle);
+ Assert.Equal(string.Empty, item.Tagline);
+ Assert.Equal(0, item.RunTimeTicks);
+ Assert.Equal("46639", item.ProviderIds["tmdb"]);
+ Assert.Equal("253573", item.ProviderIds["tvdb"]);
+
+ Assert.Equal(3, item.Genres.Length);
+ Assert.Contains("Drama", item.Genres);
+ Assert.Contains("Mystery", item.Genres);
+ Assert.Contains("Sci-Fi & Fantasy", item.Genres);
+
+ Assert.Equal(new DateTime(2017, 4, 30), item.PremiereDate);
+ Assert.Single(item.Studios);
+ Assert.Contains("Starz", item.Studios);
+
+ Assert.Equal(6, result.People.Count);
+
+ Assert.True(result.People.All(x => x.Type == PersonType.Actor));
+
+ // Only test one actor
+ var sweeney = result.People.FirstOrDefault(x => x.Role.Equals("Mad Sweeney", StringComparison.Ordinal));
+ Assert.NotNull(sweeney);
+ Assert.Equal("Pablo Schreiber", sweeney!.Name);
+ Assert.Equal(3, sweeney!.SortOrder);
+ Assert.Equal("http://image.tmdb.org/t/p/original/uo8YljeePz3pbj7gvWXdB4gOOW4.jpg", sweeney!.ImageUrl);
+
+ Assert.Equal(new DateTime(2017, 10, 7, 14, 25, 47), item.DateCreated);
+ }
+
+ [Fact]
+ public void Fetch_WithNullItem_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<Series>();
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, "Test Data/American Gods.nfo", CancellationToken.None));
+ }
+
+ [Fact]
+ public void Fetch_NullResult_ThrowsArgumentException()
+ {
+ var result = new MetadataResult<Series>()
+ {
+ Item = new Series()
+ };
+
+ Assert.Throws<ArgumentException>(() => _parser.Fetch(result, string.Empty, CancellationToken.None));
+ }
+ }
+}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/American Gods.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/American Gods.nfo
new file mode 100644
index 000000000..b9f31f2f6
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/American Gods.nfo
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<tvshow>
+ <title>American Gods</title>
+ <originaltitle>American Gods</originaltitle>
+ <showtitle>American Gods</showtitle>
+ <sorttitle>American Gods</sorttitle>
+ <ratings>
+ <rating name="themoviedb" max="10" default="true">
+ <value>6.800000</value>
+ <votes>581</votes>
+ </rating>
+ <rating name="imdb" max="10" default="true">
+ <value>5.500000</value>
+ <votes>86352</votes>
+ </rating>
+ <rating name="metacritic" max="10">
+ <value>6.0</value>
+ <votes>22</votes>
+ </rating>
+ <rating name="tomatometerallcritics" max="10">
+ <value>7.6</value>
+ <votes>71</votes>
+ </rating>
+ <rating name="tomatometerallaudience" max="10">
+ <value>6.2</value>
+ <votes>119873</votes>
+ </rating>
+ </ratings>
+ <userrating>0</userrating>
+ <top250>0</top250>
+ <season>2</season>
+ <episode>16</episode>
+ <displayseason>-1</displayseason>
+ <displayepisode>-1</displayepisode>
+ <outline></outline>
+ <plot>An ex-con becomes the traveling partner of a conman who turns out to be one of the older gods trying to recruit troops to battle the upstart deities. Based on Neil Gaiman&apos;s fantasy novel.</plot>
+ <tagline></tagline>
+ <runtime>0</runtime>
+ <thumb aspect="poster" preview="https://assets.fanart.tv/preview/tv/253573/tvposter/american-gods-58b18cd8d667a.jpg">https://assets.fanart.tv/fanart/tv/253573/tvposter/american-gods-58b18cd8d667a.jpg</thumb>
+ <thumb aspect="poster" preview="https://assets.fanart.tv/preview/tv/253573/tvposter/american-gods-5c896dbee9d21.jpg">https://assets.fanart.tv/fanart/tv/253573/tvposter/american-gods-5c896dbee9d21.jpg</thumb>
+ <thumb aspect="poster" preview="https://assets.fanart.tv/preview/tv/253573/tvposter/american-gods-57dda913a44e0.jpg">https://assets.fanart.tv/fanart/tv/253573/tvposter/american-gods-57dda913a44e0.jpg</thumb>
+ <thumb aspect="poster" preview="https://assets.fanart.tv/preview/tv/253573/tvposter/american-gods-590c159dcbf3a.jpg">https://assets.fanart.tv/fanart/tv/253573/tvposter/american-gods-590c159dcbf3a.jpg</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/tv/253573/tvbanner/american-gods-5cbbdaa84298d.jpg">https://assets.fanart.tv/fanart/tv/253573/tvbanner/american-gods-5cbbdaa84298d.jpg</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/tv/253573/tvbanner/american-gods-5932b1ffb3522.jpg">https://assets.fanart.tv/fanart/tv/253573/tvbanner/american-gods-5932b1ffb3522.jpg</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/tv/253573/tvbanner/american-gods-5932b1ffb43e4.jpg">https://assets.fanart.tv/fanart/tv/253573/tvbanner/american-gods-5932b1ffb43e4.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/tv/253573/tvthumb/american-gods-58db45dc886f5.jpg">https://assets.fanart.tv/fanart/tv/253573/tvthumb/american-gods-58db45dc886f5.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/tv/253573/tvthumb/american-gods-5932aee79947a.jpg">https://assets.fanart.tv/fanart/tv/253573/tvthumb/american-gods-5932aee79947a.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/tv/253573/tvthumb/american-gods-5932aee799e5a.jpg">https://assets.fanart.tv/fanart/tv/253573/tvthumb/american-gods-5932aee799e5a.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/tv/253573/tvthumb/american-gods-5932aee79a2f2.jpg">https://assets.fanart.tv/fanart/tv/253573/tvthumb/american-gods-5932aee79a2f2.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/tv/253573/tvthumb/american-gods-5932aee79a7c9.jpg">https://assets.fanart.tv/fanart/tv/253573/tvthumb/american-gods-5932aee79a7c9.jpg</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/tv/253573/hdtvlogo/american-gods-58b04bdcecefd.png">https://assets.fanart.tv/fanart/tv/253573/hdtvlogo/american-gods-58b04bdcecefd.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/tv/253573/hdtvlogo/american-gods-58b04d78a7ffc.png">https://assets.fanart.tv/fanart/tv/253573/hdtvlogo/american-gods-58b04d78a7ffc.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/tv/253573/hdtvlogo/american-gods-59e6660cb7dbc.png">https://assets.fanart.tv/fanart/tv/253573/hdtvlogo/american-gods-59e6660cb7dbc.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/tv/253573/hdtvlogo/american-gods-59e6660cc0716.png">https://assets.fanart.tv/fanart/tv/253573/hdtvlogo/american-gods-59e6660cc0716.png</thumb>
+ <thumb aspect="clearart" preview="https://assets.fanart.tv/preview/tv/253573/hdclearart/american-gods-59177740ba6cd.png">https://assets.fanart.tv/fanart/tv/253573/hdclearart/american-gods-59177740ba6cd.png</thumb>
+ <thumb aspect="clearart" preview="https://assets.fanart.tv/preview/tv/253573/hdclearart/american-gods-5913b6b2ce91d.png">https://assets.fanart.tv/fanart/tv/253573/hdclearart/american-gods-5913b6b2ce91d.png</thumb>
+ <thumb aspect="clearart" preview="https://assets.fanart.tv/preview/tv/253573/hdclearart/american-gods-5913b6b2cfa64.png">https://assets.fanart.tv/fanart/tv/253573/hdclearart/american-gods-5913b6b2cfa64.png</thumb>
+ <thumb aspect="clearart" preview="https://assets.fanart.tv/preview/tv/253573/hdclearart/american-gods-5913b6b2cf502.png">https://assets.fanart.tv/fanart/tv/253573/hdclearart/american-gods-5913b6b2cf502.png</thumb>
+ <thumb aspect="clearart" preview="https://assets.fanart.tv/preview/tv/253573/hdclearart/american-gods-5a4805be0619f.png">https://assets.fanart.tv/fanart/tv/253573/hdclearart/american-gods-5a4805be0619f.png</thumb>
+ <thumb aspect="characterart" preview="https://assets.fanart.tv/preview/tv/253573/characterart/american-gods-5a4805af07a04.png">https://assets.fanart.tv/fanart/tv/253573/characterart/american-gods-5a4805af07a04.png</thumb>
+ <thumb aspect="characterart" preview="https://assets.fanart.tv/preview/tv/253573/characterart/american-gods-59e6b1c71b65a.png">https://assets.fanart.tv/fanart/tv/253573/characterart/american-gods-59e6b1c71b65a.png</thumb>
+ <thumb aspect="poster" type="season" season="2" preview="https://assets.fanart.tv/preview/tv/253573/seasonposter/american-gods-5d1274a8c31cb.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonposter/american-gods-5d1274a8c31cb.jpg</thumb>
+ <thumb aspect="poster" type="season" season="1" preview="https://assets.fanart.tv/preview/tv/253573/seasonposter/american-gods-59fea294b565f.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonposter/american-gods-59fea294b565f.jpg</thumb>
+ <thumb aspect="poster" type="season" season="1" preview="https://assets.fanart.tv/preview/tv/253573/seasonposter/american-gods-5cacdf37068db.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonposter/american-gods-5cacdf37068db.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2" preview="https://assets.fanart.tv/preview/tv/253573/seasonposter/american-gods-5cacdf7783e04.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonposter/american-gods-5cacdf7783e04.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2" preview="https://assets.fanart.tv/preview/tv/253573/seasonposter/american-gods-5d1274a8c31cb.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonposter/american-gods-5d1274a8c31cb.jpg</thumb>
+ <thumb aspect="poster" type="season" season="1" preview="https://assets.fanart.tv/preview/tv/253573/seasonposter/american-gods-59fea294b565f.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonposter/american-gods-59fea294b565f.jpg</thumb>
+ <thumb aspect="poster" type="season" season="1" preview="https://assets.fanart.tv/preview/tv/253573/seasonposter/american-gods-5cacdf37068db.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonposter/american-gods-5cacdf37068db.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2" preview="https://assets.fanart.tv/preview/tv/253573/seasonposter/american-gods-5cacdf7783e04.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonposter/american-gods-5cacdf7783e04.jpg</thumb>
+ <thumb aspect="banner" type="season" season="1" preview="https://assets.fanart.tv/preview/tv/253573/seasonbanner/american-gods-5cc6b35699d26.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonbanner/american-gods-5cc6b35699d26.jpg</thumb>
+ <thumb aspect="banner" type="season" season="2" preview="https://assets.fanart.tv/preview/tv/253573/seasonbanner/american-gods-5cc6b36965b54.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonbanner/american-gods-5cc6b36965b54.jpg</thumb>
+ <thumb aspect="banner" type="season" season="1" preview="https://assets.fanart.tv/preview/tv/253573/seasonbanner/american-gods-5cc6b35699d26.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonbanner/american-gods-5cc6b35699d26.jpg</thumb>
+ <thumb aspect="banner" type="season" season="2" preview="https://assets.fanart.tv/preview/tv/253573/seasonbanner/american-gods-5cc6b36965b54.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonbanner/american-gods-5cc6b36965b54.jpg</thumb>
+ <thumb aspect="landscape" type="season" season="2" preview="https://assets.fanart.tv/preview/tv/253573/seasonthumb/american-gods-5cc6b380d6c56.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonthumb/american-gods-5cc6b380d6c56.jpg</thumb>
+ <thumb aspect="landscape" type="season" season="1" preview="https://assets.fanart.tv/preview/tv/253573/seasonthumb/american-gods-59e6b5a03e7aa.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonthumb/american-gods-59e6b5a03e7aa.jpg</thumb>
+ <thumb aspect="landscape" type="season" season="2" preview="https://assets.fanart.tv/preview/tv/253573/seasonthumb/american-gods-5cc6b380d6c56.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonthumb/american-gods-5cc6b380d6c56.jpg</thumb>
+ <thumb aspect="landscape" type="season" season="1" preview="https://assets.fanart.tv/preview/tv/253573/seasonthumb/american-gods-59e6b5a03e7aa.jpg">https://assets.fanart.tv/fanart/tv/253573/seasonthumb/american-gods-59e6b5a03e7aa.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/m6qf6lq3yARgbZwspvDLbUFtASh.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/gevw5nZRYz2kWj1PqW9pz4sgeeZ.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/btwTe5cQbGWGOErBiRqnjNP9cJl.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/loJ4sfr4zp995qMoeCHiIIGaOg8.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/dHo8Lw7ruIaQTdTTDZPCMyZxwy5.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/zfAXP4bG2G17VuLNU9cqRcVU0xj.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/oxYUbNpG2st2zXWzYRvewehmvuj.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/mwoQ6zynu2DBxKCBYi30qoM236N.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/8XEoXAMzgcf7m1KiUDZ9N1UGh4o.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/rWsayJB1grML2LdPjjKDC3g0Brr.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/8qRsj8uJ4zPARQmQ9FvejTY1lnV.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/acjnZP0GrwWDxCxV6QejKizbzOy.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/hN1sI57QILGfdrEOqpUfo0NtHjW.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/hz2jNy3DfseYzRSybGRlUtz4pTi.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/hLDgNDdrkB0oWiuClpxN4E3XadJ.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/4FiqawHsVz1mYCRudPtXKbfmP4M.jpg</thumb>
+ <thumb aspect="poster">http://image.tmdb.org/t/p/original/sKR8Q36YBtyRc19y4yGYuD1xBgA.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2">http://image.tmdb.org/t/p/original/4l8Vnbb7e5QA6bAItMqQIHXLRgc.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2">http://image.tmdb.org/t/p/original/ni0thXw5Zi5dQKBY6Oj0vcfIS2n.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2">http://image.tmdb.org/t/p/original/v17HfCzWKQKOBrww9RxZmN5R9tF.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2">http://image.tmdb.org/t/p/original/2ffvlgYsxbXGiWkc3V6Q8tgpiBo.jpg</thumb>
+ <thumb aspect="poster" type="season" season="1">http://image.tmdb.org/t/p/original/rASj7OUjWDhfhAeO2MaFOA3lJpQ.jpg</thumb>
+ <thumb aspect="poster" type="season" season="1">http://image.tmdb.org/t/p/original/67exRijfvN5RRmBCqFtk1bhJ7Uh.jpg</thumb>
+ <thumb aspect="poster" type="season" season="1">http://image.tmdb.org/t/p/original/59iE3xxP7H8rAiXW6TDR2HSoUUm.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2">http://image.tmdb.org/t/p/original/4l8Vnbb7e5QA6bAItMqQIHXLRgc.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2">http://image.tmdb.org/t/p/original/ni0thXw5Zi5dQKBY6Oj0vcfIS2n.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2">http://image.tmdb.org/t/p/original/v17HfCzWKQKOBrww9RxZmN5R9tF.jpg</thumb>
+ <thumb aspect="poster" type="season" season="2">http://image.tmdb.org/t/p/original/2ffvlgYsxbXGiWkc3V6Q8tgpiBo.jpg</thumb>
+ <thumb aspect="banner">https://thetvdb.com/banners/graphical/253573-g3.jpg</thumb>
+ <thumb aspect="banner">https://thetvdb.com/banners/graphical/253573-g4.jpg</thumb>
+ <thumb aspect="banner">https://thetvdb.com/banners/graphical/253573-g2.jpg</thumb>
+ <thumb aspect="banner">https://thetvdb.com/banners/graphical/253573-g.jpg</thumb>
+ <thumb aspect="banner">https://thetvdb.com/banners/graphical/253573-g5.jpg</thumb>
+ <fanart>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-5c8965c58e778.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-5c8965c58e778.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-59e6a8a495c2a.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-59e6a8a495c2a.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-59e6b13827ba2.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-59e6b13827ba2.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-5932b089e07ad.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-5932b089e07ad.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-5932b089e2913.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-5932b089e2913.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-5932b089e0000.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-5932b089e0000.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-5932b089e0d3a.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-5932b089e0d3a.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-5932b089e1395.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-5932b089e1395.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-5932b089e1952.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-5932b089e1952.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/tv/253573/showbackground/american-gods-5932b089e23ca.jpg">https://assets.fanart.tv/fanart/tv/253573/showbackground/american-gods-5932b089e23ca.jpg</thumb>
+ </fanart>
+ <mpaa>Australia:MA</mpaa>
+ <playcount>0</playcount>
+ <lastplayed></lastplayed>
+ <episodeguide>
+ <url cache="tmdb-46639-en.json">http://api.themoviedb.org/3/tv/46639?api_key=6a5be4999abf74eba1f9a8311294c267&amp;language=en</url>
+ </episodeguide>
+ <id>46639</id>
+ <uniqueid type="tmdb" default="true">46639</uniqueid>
+ <uniqueid type="tvdb">253573</uniqueid>
+ <genre>Drama</genre>
+ <genre>Mystery</genre>
+ <genre>Sci-Fi &amp; Fantasy</genre>
+ <premiered>2017-04-30</premiered>
+ <year>2017</year>
+ <status></status>
+ <code></code>
+ <aired></aired>
+ <studio>Starz</studio>
+ <trailer></trailer>
+ <actor>
+ <name>Ricky Whittle</name>
+ <role>Shadow Moon</role>
+ <order>0</order>
+ <thumb>http://image.tmdb.org/t/p/original/cjeDbVfBp6Qvb3C74Dfy7BKDTQN.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Ian McShane</name>
+ <role>Mr. Wednesday</role>
+ <order>1</order>
+ <thumb>http://image.tmdb.org/t/p/original/pY9ud4BJwHekNiO4MMItPbgkdAy.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Emily Browning</name>
+ <role>Laura Moon</role>
+ <order>2</order>
+ <thumb>http://image.tmdb.org/t/p/original/fa1Kyj02wxwcdS6EHb2i27TNXvU.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Pablo Schreiber</name>
+ <role>Mad Sweeney</role>
+ <order>3</order>
+ <thumb>http://image.tmdb.org/t/p/original/uo8YljeePz3pbj7gvWXdB4gOOW4.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Bruce Langley</name>
+ <role>Technical Boy</role>
+ <order>4</order>
+ <thumb>http://image.tmdb.org/t/p/original/f4EOWUmznLqboq8Ce7jnlkHVK3Y.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Yetide Badaki</name>
+ <role>Bilquis</role>
+ <order>5</order>
+ <thumb>http://image.tmdb.org/t/p/original/qfzkREHuI1JvMxBteIAjKX8qMEr.jpg</thumb>
+ </actor>
+ <namedseason number="1">Season 1</namedseason>
+ <namedseason number="2">Season 2</namedseason>
+ <resume>
+ <position>0.000000</position>
+ <total>0.000000</total>
+ </resume>
+ <dateadded>2017-10-07 14:25:47</dateadded>
+</tvshow>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
new file mode 100644
index 000000000..f838af8d0
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<movie>
+ <title>Justice League</title>
+ <originaltitle>Justice League</originaltitle>
+ <ratings>
+ <rating name="imdb" max="10" default="true">
+ <value>6.400000</value>
+ <votes>335583</votes>
+ </rating>
+ <rating name="metacritic" max="10">
+ <value>4.500000</value>
+ <votes>52</votes>
+ </rating>
+ <rating name="themoviedb" max="10">
+ <value>6.200000</value>
+ <votes>7788</votes>
+ </rating>
+ <rating name="tomatometerallcritics" max="10">
+ <value>7.6</value>
+ <votes>71</votes>
+ </rating>
+ <rating name="tomatometerallaudience" max="10">
+ <value>6.2</value>
+ <votes>119873</votes>
+ </rating>
+ </ratings>
+ <userrating>0</userrating>
+ <top250>0</top250>
+ <outline>Fueled by his restored faith in humanity and inspired by Superman&apos;s selfless act, Bruce Wayne enlists the help of his new-found ally, Diana Prince, to face an even greater enemy.</outline>
+ <plot>Fueled by his restored faith in humanity and inspired by Superman&apos;s selfless act, Bruce Wayne enlists the help of his newfound ally, Diana Prince, to face an even greater enemy. Together, Batman and Wonder Woman work quickly to find and recruit a team of meta-humans to stand against this newly awakened threat. But despite the formation of this unprecedented league of heroes-Batman, Wonder Woman, Aquaman, Cyborg and The Flash-it may already be too late to save the planet from an assault of catastrophic proportions.</plot>
+ <tagline>Justice for all.</tagline>
+ <runtime>120</runtime>
+ <thumb aspect="set.poster" preview="https://assets.fanart.tv/preview/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg">https://assets.fanart.tv/fanart/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg</thumb>
+ <thumb aspect="set.poster" preview="https://assets.fanart.tv/preview/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg">https://assets.fanart.tv/fanart/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg</thumb>
+ <thumb aspect="set.clearlogo" preview="https://assets.fanart.tv/preview/movies/468551/hdmovielogo/justice-league-collection-5ba855ed4239a.png">https://assets.fanart.tv/fanart/movies/468551/hdmovielogo/justice-league-collection-5ba855ed4239a.png</thumb>
+ <thumb aspect="set.clearart" preview="https://assets.fanart.tv/preview/movies/468551/hdmovieclearart/justice-league-collection-5c24eae8d4d71.png">https://assets.fanart.tv/fanart/movies/468551/hdmovieclearart/justice-league-collection-5c24eae8d4d71.png</thumb>
+ <thumb aspect="set.landscape" preview="https://assets.fanart.tv/preview/movies/468551/moviethumb/justice-league-collection-5c24ebc7d0d2b.jpg">https://assets.fanart.tv/fanart/movies/468551/moviethumb/justice-league-collection-5c24ebc7d0d2b.jpg</thumb>
+ <thumb aspect="set.poster" preview="http://image.tmdb.org/t/p/w500/cigoYpXWgYYgsIsEPwMneVYpuwo.jpg">http://image.tmdb.org/t/p/original/cigoYpXWgYYgsIsEPwMneVYpuwo.jpg</thumb>
+ <thumb aspect="set.poster" preview="http://image.tmdb.org/t/p/w500/2ZOCiOdAOVSKsFjC1Vl2ZlANrPj.jpg">http://image.tmdb.org/t/p/original/2ZOCiOdAOVSKsFjC1Vl2ZlANrPj.jpg</thumb>
+ <thumb aspect="set.poster" preview="http://image.tmdb.org/t/p/w500/cigoYpXWgYYgsIsEPwMneVYpuwo.jpg">http://image.tmdb.org/t/p/original/cigoYpXWgYYgsIsEPwMneVYpuwo.jpg</thumb>
+ <thumb aspect="set.poster" preview="http://image.tmdb.org/t/p/w500/2ZOCiOdAOVSKsFjC1Vl2ZlANrPj.jpg">http://image.tmdb.org/t/p/original/2ZOCiOdAOVSKsFjC1Vl2ZlANrPj.jpg</thumb>
+ <thumb aspect="set.poster" preview="http://image.tmdb.org/t/p/w500/7iO1C4c5igXsB6AyxnqsCPv6fiq.jpg">http://image.tmdb.org/t/p/original/7iO1C4c5igXsB6AyxnqsCPv6fiq.jpg</thumb>
+ <thumb aspect="set.fanart" preview="http://image.tmdb.org/t/p/w500/vyxOJuk6cxrRcGzuMRbDTpwji1w.jpg">http://image.tmdb.org/t/p/original/vyxOJuk6cxrRcGzuMRbDTpwji1w.jpg</thumb>
+ <thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg">http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg</thumb>
+ <thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/eRoXqOzciHkSPs1Z8pGnJMZo0Zb.jpg">http://image.tmdb.org/t/p/original/eRoXqOzciHkSPs1Z8pGnJMZo0Zb.jpg</thumb>
+ <thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/aQkEdqaXVxYObMLeoBSAUcgkxLs.jpg">http://image.tmdb.org/t/p/original/aQkEdqaXVxYObMLeoBSAUcgkxLs.jpg</thumb>
+ <thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/4lo1fTexk2eNIeQx3Tp74kN7ASE.jpg">http://image.tmdb.org/t/p/original/4lo1fTexk2eNIeQx3Tp74kN7ASE.jpg</thumb>
+ <thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/fF94rvT1kJpVzIE2aSYgYj9B3pc.jpg">http://image.tmdb.org/t/p/original/fF94rvT1kJpVzIE2aSYgYj9B3pc.jpg</thumb>
+ <thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/uwegp70cWe16EtwsSjbL6ShPenG.jpg">http://image.tmdb.org/t/p/original/uwegp70cWe16EtwsSjbL6ShPenG.jpg</thumb>
+ <thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/exLtrlI7JjKcfQVTccI7XdQRFMz.jpg">http://image.tmdb.org/t/p/original/exLtrlI7JjKcfQVTccI7XdQRFMz.jpg</thumb>
+ <thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/paLcue01KpfQftorfjKqqD4qvlL.jpg">http://image.tmdb.org/t/p/original/paLcue01KpfQftorfjKqqD4qvlL.jpg</thumb>
+ <thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/yVDIfiKIsCbdFcgLXW34bAsnQvy.jpg">http://image.tmdb.org/t/p/original/yVDIfiKIsCbdFcgLXW34bAsnQvy.jpg</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-57b476a831d74.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-57b476a831d74.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-57947e28cf10b.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-57947e28cf10b.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5863d5c0cf0c9.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5863d5c0cf0c9.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5a801747e5545.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5a801747e5545.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5cd75683df92b.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5cd75683df92b.png</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/movies/141052/moviebanner/justice-league-586017e95adbd.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/movies/141052/moviebanner/justice-league-5934d45bc6592.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-5934d45bc6592.jpg</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/movies/141052/moviebanner/justice-league-5aa9289a379fa.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-5aa9289a379fa.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-585fb155c3743.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-585edbda91d82.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585edbda91d82.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-5b86588882c12.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-5b86588882c12.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-5bbb9babe600c.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-5bbb9babe600c.jpg</thumb>
+ <thumb aspect="clearart" preview="https://assets.fanart.tv/preview/movies/141052/hdmovieclearart/justice-league-5865c23193041.png">https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a3af26360617.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-58690967b9765.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-58690967b9765.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a953ca4db6a6.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a953ca4db6a6.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a0b913c233be.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a0b913c233be.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-59dc595362ef1.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-59dc595362ef1.png</thumb>
+ <fanart>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-58fa1f1932897.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-58fa1f1932897.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a14f5fd8dd16.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a14f5fd8dd16.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a119394ea362.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a119394ea362.jpg</thumb>
+ </fanart>
+ <mpaa>Australia:M</mpaa>
+ <playcount>0</playcount>
+ <lastplayed></lastplayed>
+ <id>tt0974015</id>
+ <uniqueid type="imdb" default="true">tt0974015</uniqueid>
+ <genre>Action</genre>
+ <genre>Adventure</genre>
+ <genre>Fantasy</genre>
+ <genre>Sci-Fi</genre>
+ <country>USA</country>
+ <country>Canada</country>
+ <country>UK</country>
+ <set>
+ <name>Justice League Collection</name>
+ <overview>Based on the DC Comics superhero team</overview>
+ </set>
+ <credits>Jerry Siegel</credits>
+ <credits>Joe Shuster</credits>
+ <director>Zack Snyder</director>
+ <premiered>2017-11-15</premiered>
+ <year>2017</year>
+ <status></status>
+ <code></code>
+ <aired></aired>
+ <studio>DC Comics</studio>
+ <trailer></trailer>
+ <fileinfo>
+ <streamdetails>
+ <video>
+ <codec>h264</codec>
+ <aspect>1.777778</aspect>
+ <width>1920</width>
+ <height>1080</height>
+ <durationinseconds>6268</durationinseconds>
+ <stereomode></stereomode>
+ </video>
+ <audio>
+ <codec>truehd</codec>
+ <language>eng</language>
+ <channels>8</channels>
+ </audio>
+ <audio>
+ <codec>ac3</codec>
+ <language></language>
+ <channels>6</channels>
+ </audio>
+ <subtitle>
+ <language>eng</language>
+ </subtitle>
+ </streamdetails>
+ </fileinfo>
+ <actor>
+ <name>Ben Affleck</name>
+ <role>Batman</role>
+ <order>0</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMTI4MzIxMTk0Nl5BMl5BanBnXkFtZTcwOTU5NjA0Mg@@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Henry Cavill</name>
+ <role>Superman</role>
+ <order>1</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMTUxNTExMzUzOF5BMl5BanBnXkFtZTgwOTI1MjA3OTE@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Amy Adams</name>
+ <role>Lois Lane</role>
+ <order>2</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMTg2NTk2MTgxMV5BMl5BanBnXkFtZTgwNjcxMjAzMTI@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Gal Gadot</name>
+ <role>Wonder Woman</role>
+ <order>3</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMjUzZTJmZDItODRjYS00ZGRhLTg2NWQtOGE0YjJhNWVlMjNjXkEyXkFqcGdeQXVyMTg4NDI0NDM@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Ezra Miller</name>
+ <role>The Flash</role>
+ <order>4</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMjEwMjQ3ODgxOV5BMl5BanBnXkFtZTgwNzc4NjE4NTE@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Jason Momoa</name>
+ <role>Aquaman</role>
+ <order>5</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMTI5MTU5NjM1MV5BMl5BanBnXkFtZTcwODc4MDk0Mw@@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Ray Fisher</name>
+ <role>Cyborg</role>
+ <order>6</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BYzdhMzkyYTgtMjQzMC00ODhmLWExZmItNTU4MDVlMzY2NzgwXkEyXkFqcGdeQXVyNzA5NjQ5MDk@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Jeremy Irons</name>
+ <role>Alfred</role>
+ <order>7</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMTY5Mzg2NDY5OV5BMl5BanBnXkFtZTcwMDQwNzA0Mg@@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Diane Lane</name>
+ <role>Martha Kent</role>
+ <order>8</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMzM5ODM1ZWMtZjcyYy00MzgzLWJmMGQtZWY5OGQyNTRiODIxXkEyXkFqcGdeQXVyOTE0NjgwMjY@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Connie Nielsen</name>
+ <role>Queen Hippolyta</role>
+ <order>9</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BYzZiYTQ4YTAtMzRkMi00ZDZlLWFkZWItNGI2ZTIyODRiYTc4XkEyXkFqcGdeQXVyMjUzMjc2MjE@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>J.K. Simmons</name>
+ <role>Commissioner Gordon</role>
+ <order>10</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMzg2NTI5NzQ1MV5BMl5BanBnXkFtZTgwNjI1NDEwMDI@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Ciarán Hinds</name>
+ <role>Steppenwolf</role>
+ <order>11</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMTIyNjM0MzU0NF5BMl5BanBnXkFtZTcwOTIxMzg1MQ@@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Amber Heard</name>
+ <role>Mera</role>
+ <order>12</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMjA4NDkyODA3M15BMl5BanBnXkFtZTgwMzUzMjYzNzM@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Joe Morton</name>
+ <role>Silas Stone</role>
+ <order>13</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BMTQ1MjYwMTQ2MF5BMl5BanBnXkFtZTgwNzI4MTA0NDE@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Lisa Loven Kongsli</name>
+ <role>Menalippe</role>
+ <order>14</order>
+ <thumb>https://m.media-amazon.com/images/M/MV5BOTFjOTFhNTgtZjk3Ny00MTNjLWE3MWUtMWI3ZWM5NDljZjQwXkEyXkFqcGdeQXVyMjQwMDg0Ng@@._V1_SX1024_SY1024_.jpg</thumb>
+ </actor>
+ <resume>
+ <position>0.000000</position>
+ <total>0.000000</total>
+ </resume>
+ <showlink>Justice League</showlink>
+ <dateadded>2019-08-06 09:01:18</dateadded>
+</movie>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Season 01.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Season 01.nfo
new file mode 100644
index 000000000..91f0392f4
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Season 01.nfo
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<season>
+ <plot />
+ <outline />
+ <lockdata>false</lockdata>
+ <dateadded>2020-06-14 17:26:51</dateadded>
+ <title>Season 1</title>
+ <year>2019</year>
+ <tvdbid>359728</tvdbid>
+ <premiered>2019-11-08</premiered>
+ <releasedate>2019-11-08</releasedate>
+ <art>
+ <poster>/media/Serien/High School Musical The Musical The Series (2019)/Season 1/Season 1.jpeg</poster>
+ </art>
+ <actor>
+ <name>Olivia Rodrigo</name>
+ <role>Nini</role>
+ <type>Actor</type>
+ <sortorder>0</sortorder>
+ <thumb>/config/metadata/People/O/Olivia Rodrigo/poster.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Kate Reinders</name>
+ <role>Miss Jenn</role>
+ <type>Actor</type>
+ <sortorder>1</sortorder>
+ <thumb>/config/metadata/People/K/Kate Reinders/poster.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Sofia Wylie</name>
+ <role>Gina</role>
+ <type>Actor</type>
+ <sortorder>2</sortorder>
+ <thumb>/config/metadata/People/S/Sofia Wylie/poster.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Matt Cornett</name>
+ <role>E.J.</role>
+ <type>Actor</type>
+ <sortorder>3</sortorder>
+ <thumb>/config/metadata/People/M/Matt Cornett/poster.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Dara Reneé</name>
+ <role>Kourtney</role>
+ <type>Actor</type>
+ <sortorder>4</sortorder>
+ <thumb>/config/metadata/People/D/Dara Reneé/poster.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Julia Lester</name>
+ <role>Ashlyn</role>
+ <type>Actor</type>
+ <sortorder>5</sortorder>
+ <thumb>/config/metadata/People/J/Julia Lester/poster.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Joshua Bassett</name>
+ <role>Ricky</role>
+ <type>Actor</type>
+ <sortorder>6</sortorder>
+ <thumb>/config/metadata/People/J/Joshua Bassett/poster.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Frankie A. Rodriguez</name>
+ <role>Carlos</role>
+ <type>Actor</type>
+ <sortorder>7</sortorder>
+ <thumb>/config/metadata/People/F/Frankie A. Rodriguez/poster.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Larry Saperstein</name>
+ <role>Big Red</role>
+ <type>Actor</type>
+ <sortorder>8</sortorder>
+ <thumb>/config/metadata/People/L/Larry Saperstein/poster.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Mark St. Cyr</name>
+ <role>Mr. Mazzara</role>
+ <type>Actor</type>
+ <sortorder>9</sortorder>
+ <thumb>/config/metadata/People/M/Mark St. Cyr/poster.jpg</thumb>
+ </actor>
+ <seasonnumber>1</seasonnumber>
+</season>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Best of 1980-1990.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Best of 1980-1990.nfo
new file mode 100644
index 000000000..4ab8400d3
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Best of 1980-1990.nfo
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<album>
+ <title>The Best of 1980-1990</title>
+ <musicbrainzalbumid>59b5a40b-e2fd-3f18-a218-e8c9aae12ab5</musicbrainzalbumid>
+ <musicbrainzreleasegroupid>6c301dbd-6ccb-3403-a6c4-6a22240a0297</musicbrainzreleasegroupid>
+ <scrapedmbid>false</scrapedmbid>
+ <artistdesc>U2</artistdesc>
+ <genre>Pop</genre>
+ <style>Rock/Pop</style>
+ <mood>Political</mood>
+ <compilation>false</compilation>
+ <review>The Best of 1980-1990 is the first greatest hits compilation by Irish rock band U2, released in November 1998. It mostly contains the group&apos;s hit singles from the eighties but also mixes in some live staples as well as one new recording, Sweetest Thing. In April 1999, a companion video (featuring music videos and live footage) was released. The album was followed by another compilation, The Best of 1990-2000, in 2002.&#x0A;A limited edition version containing a special B-sides disc was released on the same date as the single-disc version. At the time of release, the official word was that the 2-disc album would be available the first week the album went on sale, then pulled from the stores. While this threat never materialized, it did result in the 2-disc version being in very high demand. Both versions charted in the Billboard 200.&#x0A;The boy on the cover is Peter Rowan, brother of Bono&apos;s friend Guggi (real name Derek Rowan) of the Virgin Prunes. He also appears on the covers of the early EP Three, two of the band&apos;s first three albums (Boy and War), and Early Demos.</review>
+ <type>album / compilation</type>
+ <releasedate></releasedate>
+ <label>Island</label>
+ <thumb preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/albumcover/the-best-of-1980-1990-4e43a22cab023.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/albumcover/the-best-of-1980-1990-4e43a22cab023.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/albumcover/the-best-of-1980-1990-5bc4301068645.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/albumcover/the-best-of-1980-1990-5bc4301068645.jpg</thumb>
+ <thumb preview="https://www.theaudiodb.com/images/media/album/thumb/the-best-of-1980-1990-4e43a22cab023.jpg/preview">https://www.theaudiodb.com/images/media/album/thumb/the-best-of-1980-1990-4e43a22cab023.jpg</thumb>
+ <path>C:\KODI\Test- Music\U2\Best Of 1980-1990, The\</path>
+ <rating max="10">-1.000000</rating>
+ <userrating max="10">-1</userrating>
+ <votes>-1</votes>
+ <year>1989</year>
+ <albumArtistCredits>
+ <artist>U2</artist>
+ <musicBrainzArtistID>a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432</musicBrainzArtistID>
+ </albumArtistCredits>
+ <releasetype>album</releasetype>
+</album>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo
new file mode 100644
index 000000000..e77c02a34
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<episodedetails>
+ <title>The Bone Orchard</title>
+ <showtitle>American Gods</showtitle>
+ <ratings>
+ <rating name="tmdb" max="10" default="true">
+ <value>7.532000</value>
+ <votes>31</votes>
+ </rating>
+ </ratings>
+ <userrating>0</userrating>
+ <top250>0</top250>
+ <season>1</season>
+ <episode>1</episode>
+ <displayseason>-1</displayseason>
+ <displayepisode>-1</displayepisode>
+ <outline></outline>
+ <plot>When Shadow Moon is released from prison early after the death of his wife, he meets Mr. Wednesday and is recruited as his bodyguard. Shadow discovers that this may be more than he bargained for.</plot>
+ <tagline></tagline>
+ <runtime>0</runtime>
+ <thumb>http://image.tmdb.org/t/p/original/uvry4weK00pFLn7fxQ9M4m3Da2A.jpg</thumb>
+ <mpaa>16</mpaa>
+ <playcount>0</playcount>
+ <lastplayed></lastplayed>
+ <id>1276153</id>
+ <uniqueid type="tmdb" default="true">1276153</uniqueid>
+ <genre>Drama</genre>
+ <genre>Mystery</genre>
+ <genre>Sci-Fi &amp; Fantasy</genre>
+ <credits>Bryan Fuller</credits>
+ <credits>Michael Green</credits>
+ <director>David Slade</director>
+ <premiered>2017-04-30</premiered>
+ <year>2017</year>
+ <status></status>
+ <code></code>
+ <aired>2017-04-30</aired>
+ <studio>Starz</studio>
+ <trailer></trailer>
+ <actor>
+ <name>Jonathan Tucker</name>
+ <role>&apos;Low Key&apos; Lyesmith</role>
+ <order>10</order>
+ <thumb>http://image.tmdb.org/t/p/original/jvJpYDbwmUTACw7Yn7PKOP6CdlJ.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Demore Barnes</name>
+ <role>Mr. Ibis</role>
+ <order>11</order>
+ <thumb>http://image.tmdb.org/t/p/original/4rEVzSIFPgiN14xYQnjKcKQ7tYE.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Betty Gilpin</name>
+ <role>Audrey</role>
+ <order>12</order>
+ <thumb>http://image.tmdb.org/t/p/original/xFeqyem5i4Kf0nFjBZ4Oi9NM26k.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Beth Grant</name>
+ <role>Jack</role>
+ <order>13</order>
+ <thumb>http://image.tmdb.org/t/p/original/zAT9GvzJE0ytL3C36L461cgKI9p.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Joel Murray</name>
+ <role>Paunch</role>
+ <order>14</order>
+ <thumb>http://image.tmdb.org/t/p/original/t5syYfCgxbTC7XPrNeXhhhQULUf.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Ricky Whittle</name>
+ <role>Shadow Moon</role>
+ <order>0</order>
+ <thumb>http://image.tmdb.org/t/p/original/cjeDbVfBp6Qvb3C74Dfy7BKDTQN.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Ian McShane</name>
+ <role>Mr. Wednesday</role>
+ <order>1</order>
+ <thumb>http://image.tmdb.org/t/p/original/pY9ud4BJwHekNiO4MMItPbgkdAy.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Emily Browning</name>
+ <role>Laura Moon</role>
+ <order>2</order>
+ <thumb>http://image.tmdb.org/t/p/original/fa1Kyj02wxwcdS6EHb2i27TNXvU.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Pablo Schreiber</name>
+ <role>Mad Sweeney</role>
+ <order>3</order>
+ <thumb>http://image.tmdb.org/t/p/original/uo8YljeePz3pbj7gvWXdB4gOOW4.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Bruce Langley</name>
+ <role>Technical Boy</role>
+ <order>4</order>
+ <thumb>http://image.tmdb.org/t/p/original/f4EOWUmznLqboq8Ce7jnlkHVK3Y.jpg</thumb>
+ </actor>
+ <actor>
+ <name>Yetide Badaki</name>
+ <role>Bilquis</role>
+ <order>5</order>
+ <thumb>http://image.tmdb.org/t/p/original/qfzkREHuI1JvMxBteIAjKX8qMEr.jpg</thumb>
+ </actor>
+ <resume>
+ <position>0.000000</position>
+ <total>0.000000</total>
+ </resume>
+ <dateadded>2017-10-07 14:25:47</dateadded>
+</episodedetails>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/U2.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/U2.nfo
new file mode 100644
index 000000000..8c46fdeb8
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/U2.nfo
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<artist>
+ <name>U2</name>
+ <musicBrainzArtistID>a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432</musicBrainzArtistID>
+ <sortname>U2</sortname>
+ <type></type>
+ <gender></gender>
+ <disambiguation>Irish rock band</disambiguation>
+ <genre>Rock</genre>
+ <style>Rock/Pop</style>
+ <mood>Political</mood>
+ <born></born>
+ <formed>Dublin, Ireland (1976)</formed>
+ <biography>U2 are an Irish rock band from Dublin. Formed in 1976, the group consists of Bono (vocals and rhythm guitar), the Edge (lead guitar, keyboards, and vocals), Adam Clayton (bass guitar), and Larry Mullen, Jr. (drums and percussion). U2&apos;s early sound was rooted in post-punk but eventually grew to incorporate influences from many genres of popular music. Throughout the group&apos;s musical pursuits, they have maintained a sound built on melodic instrumentals. Their lyrics, often embellished with spiritual imagery, focus on personal themes and sociopolitical concerns.&#x0A;The band formed at Mount Temple Comprehensive School in 1976 when the members were teenagers with limited musical proficiency. Within four years, they signed with Island Records and released their debut album Boy. By the mid-1980s, U2 had become a top international act. They were more successful as a touring act than they were at selling records until their 1987 album The Joshua Tree which, according to Rolling Stone, elevated the band&apos;s stature &quot;from heroes to superstars&quot;. Reacting to musical stagnation and criticism of their earnest image and musical direction in the late 1980s, U2 reinvented themselves with their 1991 album, Achtung Baby, and the accompanying Zoo TV Tour; they integrated dance, industrial, and alternative rock influences into their sound, and embraced a more ironic and self-deprecating image. They embraced similar experimentation for the remainder of the 1990s with varying levels of success. U2 regained critical and commercial favour in the 2000s with the records All That You Can&apos;t Leave Behind (2000) and How to Dismantle an Atomic Bomb (2004), which established a more conventional, mainstream sound for the group. Their U2 360° Tour of 2009–2011 is the highest-attended and highest-grossing concert tour in history.&#x0A;U2 have released 13 studio albums and are one of the world&apos;s best-selling music artists of all time, having sold more than 170 million records worldwide. They have won 22 Grammy Awards, more than any other band; and, in 2005, were inducted into the Rock and Roll Hall of Fame in their first year of eligibility. Rolling Stone ranked U2 at number 22 in its list of the &quot;100 Greatest Artists of All Time&quot;, and labelled them the &quot;Biggest Band in the World&quot;. Throughout their career, as a band and as individuals, they have campaigned for human rights and philanthropic causes, including Amnesty International, the ONE/DATA campaigns, Product Red, War Child and the Edge&apos;s Music Rising.</biography>
+ <died></died>
+ <disbanded></disbanded>
+ <thumb aspect="poster" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistthumb/u2-50104c356fd2b.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistthumb/u2-50104c356fd2b.jpg</thumb>
+ <thumb aspect="poster" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistthumb/u2-4fdf7ab5bfa99.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistthumb/u2-4fdf7ab5bfa99.jpg</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/hdmusiclogo/u2-538b320283c98.png">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/hdmusiclogo/u2-538b320283c98.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/hdmusiclogo/u2-538b3268cc581.png">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/hdmusiclogo/u2-538b3268cc581.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/hdmusiclogo/u2-55b02a97170c7.png">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/hdmusiclogo/u2-55b02a97170c7.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/hdmusiclogo/u2-538b39954caf1.png">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/hdmusiclogo/u2-538b39954caf1.png</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/musicbanner/u2-59e65cba172de.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/musicbanner/u2-59e65cba172de.jpg</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/musicbanner/u2-54063da8ca135.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/musicbanner/u2-54063da8ca135.jpg</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/musicbanner/u2-503f9e062c802.JPG">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/musicbanner/u2-503f9e062c802.JPG</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/musicbanner/u2-591ce819c91a5.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/musicbanner/u2-591ce819c91a5.jpg</thumb>
+ <thumb preview="https://www.theaudiodb.com/images/media/artist/thumb/qvuxvs1347997318.jpg/preview">https://www.theaudiodb.com/images/media/artist/thumb/qvuxvs1347997318.jpg</thumb>
+ <thumb aspect="clearlogo" preview="https://www.theaudiodb.com/images/media/artist/logo/qywsvv1347997327.png/preview">https://www.theaudiodb.com/images/media/artist/logo/qywsvv1347997327.png</thumb>
+ <thumb aspect="clearart" preview="https://www.theaudiodb.com/images/media/artist/clearart/vwpyxv1511531849.png/preview">https://www.theaudiodb.com/images/media/artist/clearart/vwpyxv1511531849.png</thumb>
+ <thumb aspect="landscape" preview="https://www.theaudiodb.com/images/media/artist/widethumb/wxsxwq1524669620.jpg/preview">https://www.theaudiodb.com/images/media/artist/widethumb/wxsxwq1524669620.jpg</thumb>
+ <thumb aspect="banner" preview="https://www.theaudiodb.com/images/media/artist/banner/rpqwpu1488384726.jpg/preview">https://www.theaudiodb.com/images/media/artist/banner/rpqwpu1488384726.jpg</thumb>
+ <path>E:\z-Music Artists\U2</path>
+ <fanart>
+ <thumb preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-4f805e377b181.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-4f805e377b181.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-5058bffb80200.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-5058bffb80200.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-4f805e377b5a0.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-4f805e377b5a0.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-4df96804ad0f3.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-4df96804ad0f3.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-5487022bd1524.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-5487022bd1524.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-50104bf699b84.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-50104bf699b84.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-4f805e377acdb.jpg">https://assets.fanart.tv/fanart/music/a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432/artistbackground/u2-4f805e377acdb.jpg</thumb>
+ </fanart>
+ <album>
+ <title>Pop</title>
+ <year>1997</year>
+ </album>
+ <album>
+ <title>How to Dismantle an Atomic Bomb</title>
+ <year>2004</year>
+ </album>
+ <album>
+ <title>Boy</title>
+ <year>1980</year>
+ </album>
+ <album>
+ <title>Pop</title>
+ <year>1997</year>
+ </album>
+ <album>
+ <title>The Joshua Tree</title>
+ <year>1987</year>
+ </album>
+ <album>
+ <title>Achtung Baby</title>
+ <year>1991</year>
+ </album>
+ <album>
+ <title>Zooropa</title>
+ <year>1993</year>
+ </album>
+</artist>