Files
ersatztv/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/EpisodeNfoReaderTests.cs
T
Jason DoveandGitHub aeda5050d3 nvidia decoder fixes (#2041)
* replace FluentAssertions with Shouldly

* fix song transcoding tests

* only specify hwaccel when hardware decode is required

* update changelog
2025-06-17 18:26:49 +00:00

499 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text;
using Bugsnag;
using ErsatzTV.Core;
using ErsatzTV.Scanner.Core.Metadata.Nfo;
using Shouldly;
using Microsoft.Extensions.Logging;
using Microsoft.IO;
using NSubstitute;
using NUnit.Framework;
using Serilog;
namespace ErsatzTV.Scanner.Tests.Core.Metadata.Nfo;
[TestFixture]
public class EpisodeNfoReaderTests
{
[SetUp]
public void SetUp() => _episodeNfoReader = new EpisodeNfoReader(
new RecyclableMemoryStreamManager(),
Substitute.For<IClient>(),
_logger);
private readonly ILogger<EpisodeNfoReader> _logger;
public EpisodeNfoReaderTests()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
ILoggerFactory loggerFactory = new LoggerFactory().AddSerilog(Log.Logger);
_logger = loggerFactory.CreateLogger<EpisodeNfoReader>();
}
private EpisodeNfoReader _episodeNfoReader;
[Test]
public async Task One()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(1);
}
}
[Test]
public async Task Two()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<showtitle>show</showtitle>
<title>episode-one</title>
<episode>1</episode>
<season>1</season>
</episodedetails>
<episodedetails>
<showtitle>show</showtitle>
<title>episode-two</title>
<episode>2</episode>
<season>1</season>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(2);
list.All(nfo => nfo.ShowTitle == "show").ShouldBeTrue();
list.All(nfo => nfo.Season == 1).ShouldBeTrue();
list.Count(nfo => nfo.Title == "episode-one" && nfo.Episode == 1).ShouldBe(1);
list.Count(nfo => nfo.Title == "episode-two" && nfo.Episode == 2).ShouldBe(1);
}
}
[Test]
public async Task UniqueIds()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<uniqueid default=""true"" type=""tvdb"">12345</uniqueid>
<uniqueid default=""false"" type=""imdb"">tt54321</uniqueid>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(1);
list[0].UniqueIds.Count.ShouldBe(2);
list[0].UniqueIds.Count(id => id.Default && id.Type == "tvdb" && id.Guid == "12345").ShouldBe(1);
list[0].UniqueIds.Count(id => !id.Default && id.Type == "imdb" && id.Guid == "tt54321").ShouldBe(1);
}
}
[Test]
public async Task No_ContentRating()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<mpaa/>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(1);
list[0].ContentRating.ShouldBeNullOrEmpty();
}
}
[Test]
public async Task ContentRating()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<mpaa>US:Something</mpaa>
</episodedetails>
<episodedetails>
<mpaa>US:Something / US:SomethingElse</mpaa>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(2);
list.Count(nfo => nfo.ContentRating == "US:Something").ShouldBe(1);
list.Count(nfo => nfo.ContentRating == "US:Something / US:SomethingElse").ShouldBe(1);
}
}
[Test]
public async Task No_Plot()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<plot/>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(1);
list[0].Plot.ShouldBeNullOrEmpty();
}
}
[Test]
public async Task Plot()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<plot>Some Plot</plot>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(1);
list[0].Plot.ShouldBe("Some Plot");
}
}
[Test]
public async Task Actors()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<actor>
<name>Name 1</name>
<role>Role 1</role>
<thumb>Thumb 1</thumb>
</actor>
<actor>
<name>Name 2</name>
<role>Role 2</role>
<thumb>Thumb 2</thumb>
</actor>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(1);
list[0].Actors.Count.ShouldBe(2);
list[0].Actors.Count(a => a.Name == "Name 1" && a.Role == "Role 1" && a.Thumb == "Thumb 1")
.ShouldBe(1);
list[0].Actors.Count(a => a.Name == "Name 2" && a.Role == "Role 2" && a.Thumb == "Thumb 2")
.ShouldBe(1);
}
}
[Test]
public async Task Writers()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<credits>Writer 1</credits>
</episodedetails>
<episodedetails>
<credits>Writer 2</credits>
<credits>Writer 3</credits>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(2);
list.Count(nfo => nfo.Writers.Count == 1 && nfo.Writers[0] == "Writer 1").ShouldBe(1);
list.Count(nfo => nfo.Writers.Count == 2 && nfo.Writers[0] == "Writer 2" && nfo.Writers[1] == "Writer 3")
.ShouldBe(1);
}
}
[Test]
public async Task Directors()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<director>Director 1</director>
</episodedetails>
<episodedetails>
<director>Director 2</director>
<director>Director 3</director>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(2);
list.Count(nfo => nfo.Directors.Count == 1 && nfo.Directors[0] == "Director 1").ShouldBe(1);
list.Count(
nfo => nfo.Directors.Count == 2 && nfo.Directors[0] == "Director 2" &&
nfo.Directors[1] == "Director 3")
.ShouldBe(1);
}
}
[Test]
public async Task Genres()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<genre>Genre 1</genre>
</episodedetails>
<episodedetails>
<genre>Genre 2</genre>
<genre>Genre 3</genre>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(2);
list.Count(nfo => nfo.Genres is ["Genre 1"]).ShouldBe(1);
list.Count(nfo => nfo.Genres is ["Genre 2", "Genre 3"]).ShouldBe(1);
}
}
[Test]
public async Task Tags()
{
var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<!--created on whatever - comment-->
<episodedetails>
<tag>Tag 1</tag>
</episodedetails>
<episodedetails>
<tag>Tag 2</tag>
<tag>Tag 3</tag>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(2);
list.Count(nfo => nfo.Tags is ["Tag 1"]).ShouldBe(1);
list.Count(nfo => nfo.Tags is ["Tag 2", "Tag 3"]).ShouldBe(1);
}
}
[Test]
public async Task FullSample_Should_Return_Nfo()
{
await using var stream = new MemoryStream(
Encoding.UTF8.GetBytes(
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes"" ?>
<episodedetails>
<title>Filmed Before a Live Studio Audience</title>
<showtitle>WandaVision</showtitle>
<ratings>
<rating name=""imdb"" max=""10"" default=""true"">
<value>7.500000</value>
<votes>18766</votes>
</rating>
<rating name=""tmdb"" max=""10"">
<value>7.500000</value>
<votes>42</votes>
</rating>
<rating name=""trakt"" max=""10"">
<value>6.952780</value>
<votes>3621</votes>
</rating>
</ratings>
<userrating>0</userrating>
<top250>0</top250>
<season>1</season>
<episode>1</episode>
<displayseason>-1</displayseason>
<displayepisode>-1</displayepisode>
<outline></outline>
<plot>Wanda and Vision struggle to conceal their powers during dinner with Visions boss and his wife.</plot>
<tagline></tagline>
<runtime>26</runtime>
<thumb spoof="""" cache="""" aspect=""thumb"" preview=""https://image.tmdb.org/t/p/w780/cbe8l0Hnbvu07ePgoOopyWYrcdL.jpg"">https://image.tmdb.org/t/p/original/cbe8l0Hnbvu07ePgoOopyWYrcdL.jpg</thumb>
<thumb spoof="""" cache="""" aspect=""thumb"" preview=""https://image.tmdb.org/t/p/w780/oNCzeCXFanVEWNpzRzyffhLLfZs.jpg"">https://image.tmdb.org/t/p/original/oNCzeCXFanVEWNpzRzyffhLLfZs.jpg</thumb>
<mpaa>Australia:TV-14</mpaa>
<playcount>1</playcount>
<lastplayed>2021-03-27</lastplayed>
<id>1830976</id>
<uniqueid type=""imdb"">tt9601584</uniqueid>
<uniqueid type=""tmdb"" default=""true"">1830976</uniqueid>
<uniqueid type=""tvdb"">8042515</uniqueid>
<genre>Sci-Fi &amp; Fantasy</genre>
<genre>Mystery</genre>
<genre>Drama</genre>
<credits>Jac Schaeffer</credits>
<director>Matt Shakman</director>
<premiered>2021-01-15</premiered>
<year>2021</year>
<status></status>
<code></code>
<aired>2021-01-15</aired>
<studio>Disney+ (US)</studio>
<trailer></trailer>
<fileinfo>
<streamdetails>
<video>
<codec>h264</codec>
<aspect>1.777778</aspect>
<width>1280</width>
<height>720</height>
<durationinseconds>1593</durationinseconds>
<stereomode></stereomode>
</video>
<audio>
<codec>aac</codec>
<language>eng</language>
<channels>2</channels>
</audio>
</streamdetails>
</fileinfo>
<actor>
<name>Randall Park</name>
<role>Jimmy Woo</role>
<order>4</order>
<thumb>https://image.tmdb.org/t/p/original/1QJ4cBQZoOaLR8Hc3V2NgBLvB0f.jpg</thumb>
</actor>
<actor>
<name>Kat Dennings</name>
<role>Darcy Lewis / The Escape Artist</role>
<order>5</order>
<thumb>https://image.tmdb.org/t/p/original/rrfyo9z1wW5nY9ZsFlj1Ozfj9g2.jpg</thumb>
</actor>
<resume>
<position>0.000000</position>
<total>0.000000</total>
</resume>
<dateadded>2021-02-02 11:57:44</dateadded>
</episodedetails>"));
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.Read(stream);
result.IsRight.ShouldBeTrue();
foreach (EpisodeNfo nfo in result.RightToSeq().Flatten())
{
nfo.ShowTitle.ShouldBe("WandaVision");
nfo.Title.ShouldBe("Filmed Before a Live Studio Audience");
nfo.Episode.ShouldBe(1);
nfo.Season.ShouldBe(1);
nfo.ContentRating.ShouldBe("Australia:TV-14");
nfo.Aired.IsSome.ShouldBeTrue();
foreach (DateTime aired in nfo.Aired)
{
aired.ShouldBe(new DateTime(2021, 01, 15));
}
nfo.Plot.ShouldBe(
"Wanda and Vision struggle to conceal their powers during dinner with Visions boss and his wife.");
nfo.Actors.ShouldBeEquivalentTo(
new List<ActorNfo>
{
new()
{
Name = "Randall Park", Order = 4, Role = "Jimmy Woo",
Thumb = "https://image.tmdb.org/t/p/original/1QJ4cBQZoOaLR8Hc3V2NgBLvB0f.jpg"
},
new()
{
Name = "Kat Dennings", Order = 5, Role = "Darcy Lewis / The Escape Artist",
Thumb = "https://image.tmdb.org/t/p/original/rrfyo9z1wW5nY9ZsFlj1Ozfj9g2.jpg"
}
});
nfo.Writers.ShouldBeEquivalentTo(new List<string> { "Jac Schaeffer" });
nfo.Directors.ShouldBeEquivalentTo(new List<string> { "Matt Shakman" });
nfo.UniqueIds.ShouldBeEquivalentTo(
new List<UniqueIdNfo>
{
new() { Type = "imdb", Guid = "tt9601584", Default = false },
new() { Type = "tmdb", Guid = "1830976", Default = true },
new() { Type = "tvdb", Guid = "8042515", Default = false }
});
}
}
[Test]
public async Task Invalid_Characters_Should_Abort_And_Return_Nfo()
{
string sourceFile = Path.Combine(
TestContext.CurrentContext.TestDirectory,
"Resources",
"Nfo",
"EpisodeInvalidCharacters.nfo");
Either<BaseError, List<EpisodeNfo>> result = await _episodeNfoReader.ReadFromFile(sourceFile);
result.IsRight.ShouldBeTrue();
foreach (List<EpisodeNfo> list in result.RightToSeq())
{
list.Count.ShouldBe(1);
list[0].Title.ShouldBe("Test Title");
}
}
}