diff options
Diffstat (limited to 'tests')
66 files changed, 6037 insertions, 224 deletions
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index bf95c458d..0b9d8f7c0 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,13 +15,13 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.2" /> </ItemGroup> <!-- Code Analyzers --> @@ -31,7 +31,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 7b20823ba..ee51562b4 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -12,7 +12,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> @@ -26,7 +26,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index 374a17e2f..d2087b023 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -12,8 +12,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> @@ -26,7 +26,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index d178837b2..c8c526f4d 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> @@ -21,7 +21,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index 725947577..bd8667a84 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -7,7 +7,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> @@ -27,7 +27,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonFlagEnumTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonFlagEnumTests.cs new file mode 100644 index 000000000..c8652b323 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonFlagEnumTests.cs @@ -0,0 +1,28 @@ +using System.Text.Json; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Session; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters; + +public class JsonFlagEnumTests +{ + private readonly JsonSerializerOptions _jsonOptions = new() + { + Converters = + { + new JsonFlagEnumConverter<TranscodeReason>() + } + }; + + [Theory] + [InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\"]")] + [InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported | TranscodeReason.VideoBitDepthNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\",\"VideoBitDepthNotSupported\"]")] + [InlineData((TranscodeReason)0, "[]")] + public void Serialize_Transcode_Reason(TranscodeReason transcodeReason, string output) + { + var result = JsonSerializer.Serialize(transcodeReason, _jsonOptions); + + Assert.Equal(output, result); + } +} diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj index 539a80dad..cc36dc4fa 100644 --- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj @@ -7,7 +7,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> @@ -26,7 +26,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> <ItemGroup> diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj index 23c46d0bf..de1fcab59 100644 --- a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj @@ -8,7 +8,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> @@ -27,7 +27,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index e7534e308..e186a488a 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -22,8 +22,8 @@ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> </ItemGroup> @@ -35,7 +35,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 0fc8724b6..53e1550ed 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -31,6 +31,16 @@ namespace Jellyfin.MediaEncoding.Tests.Probing public void GetFrameRate_Success(string value, float? expected) => Assert.Equal(expected, ProbeResultNormalizer.GetFrameRate(value)); + [Theory] + [InlineData(0.5f, "0/1", false)] + [InlineData(24.5f, "8/196", false)] + [InlineData(63.5f, "1/127", true)] + [InlineData(null, "1/60", false)] + [InlineData(30f, "2/120", true)] + [InlineData(59.999996f, "1563/187560", true)] + public void IsCodecTimeBaseDoubleTheFrameRate_Success(float? frameRate, string codecTimeBase, bool expected) + => Assert.Equal(expected, ProbeResultNormalizer.IsCodecTimeBaseDoubleTheFrameRate(frameRate, codecTimeBase)); + [Fact] public void GetMediaInfo_MetaData_Success() { diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs new file mode 100644 index 000000000..4748f3497 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs @@ -0,0 +1,466 @@ +using System; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Model.Tests +{ + public class StreamBuilderTests + { + [Theory] + // Chrome + [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450 + [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + // Firefox + [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Firefox", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450 + [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + // Safari + [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450 + [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450 + [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450 + // AndroidPixel + [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + // Yatse + [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] + [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + // RokuSSPlus + [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + // JellyfinMediaPlayer + [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450 + // Chrome-NoHLS + [InlineData("Chrome-NoHLS", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450 + [InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")] + [InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")] + [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + // TranscodeMedia + [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")] + [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")] + [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "DirectStream", "HLS.mp4")] + [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")] + [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")] + [InlineData("TranscodeMedia", "mkv-av1-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "DirectStream", "http")] + [InlineData("TranscodeMedia", "mkv-av1-vorbis-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "http")] + [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "DirectStream", "http")] + [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "DirectStream", "http")] + [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "http")] + // DirectMedia + [InlineData("DirectMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] + [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] + [InlineData("DirectMedia", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] + [InlineData("DirectMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] + [InlineData("DirectMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] + [InlineData("DirectMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] + [InlineData("DirectMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] + [InlineData("DirectMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] + [InlineData("DirectMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] + // LowBandwidth + [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + // Null + [InlineData("Null", "mp4-h264-aac-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)] + [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)] + [InlineData("Null", "mp4-h264-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)] + [InlineData("Null", "mp4-hevc-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit)] + [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit)] + [InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)] + [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)] + [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)] + public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "") + { + var options = await GetVideoOptions(deviceName, mediaSource); + BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); + } + + [Theory] + // Chrome + [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 <BUG: this is direct played> + [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + // Firefox + [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + // Safari + [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450 + [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450 + [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450 + // AndroidPixel + [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] + // Yatse + [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] + [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + // RokuSSPlus + [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + // JellyfinMediaPlayer + [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 + [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450 + public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "") + { + var options = await GetVideoOptions(deviceName, mediaSource); + options.AudioStreamIndex = 1; + options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count - 1; + + var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); + Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex); + Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex); + } + + [Theory] + // Chrome + [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450 + [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + // Firefox + [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 + [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + // Yatse + [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + // RokuSSPlus + [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 + public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "") + { + var options = await GetVideoOptions(deviceName, mediaSource); + var streamCount = options.MediaSources[0].MediaStreams.Count; + options.AudioStreamIndex = streamCount - 2; + options.SubtitleStreamIndex = streamCount - 1; + + var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); + Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex); + Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex); + } + + private StreamInfo? BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol) + { + if (string.IsNullOrEmpty(transcodeProtocol)) + { + transcodeProtocol = playMethod == PlayMethod.DirectStream ? "http" : "HLS.ts"; + } + + var builder = GetStreamBuilder(); + + var val = builder.BuildVideoItem(options); + Assert.NotNull(val); + + if (playMethod != null) + { + Assert.Equal(playMethod, val.PlayMethod); + } + + Assert.Equal(why, val.TranscodeReasons); + + var audioStreamIndexInput = options.AudioStreamIndex; + var targetVideoStream = val.TargetVideoStream; + var targetAudioStream = val.TargetAudioStream; + + var mediaSource = options.MediaSources.First(source => source.Id == val.MediaSourceId); + Assert.NotNull(mediaSource); + var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video); + var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio); + // TODO: check AudioStreamIndex vs options.AudioStreamIndex + var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex); + + var uri = ParseUri(val); + + if (playMethod == PlayMethod.DirectPlay) + { + // check expected container + var containers = ContainerProfile.SplitValue(mediaSource.Container); + // TODO: test transcode too + // Assert.Contains(uri.Extension, containers); + + // check expected video codec (1) + Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); + Assert.Single(val.TargetVideoCodec); + + // check expected audio codecs (1) + Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec); + Assert.Single(val.TargetAudioCodec); + // Assert.Single(val.AudioCodecs); + + if (transcodeMode == "DirectStream") + { + Assert.Equal(val.Container, uri.Extension); + } + } + else if (playMethod == PlayMethod.DirectStream || playMethod == PlayMethod.Transcode) + { + Assert.NotNull(val.Container); + Assert.NotEmpty(val.VideoCodecs); + Assert.NotEmpty(val.AudioCodecs); + + // check expected container (todo: this could be a test param) + if (transcodeProtocol == "http") + { + // Assert.Equal("webm", val.Container); + Assert.Equal(val.Container, uri.Extension); + Assert.Equal("stream", uri.Filename); + Assert.Equal("http", val.SubProtocol); + } + else if (transcodeProtocol == "HLS.mp4") + { + Assert.Equal("mp4", val.Container); + Assert.Equal("m3u8", uri.Extension); + Assert.Equal("master", uri.Filename); + Assert.Equal("hls", val.SubProtocol); + } + else + { + Assert.Equal("ts", val.Container); + Assert.Equal("m3u8", uri.Extension); + Assert.Equal("master", uri.Filename); + Assert.Equal("hls", val.SubProtocol); + } + + // Full transcode + if (transcodeMode == "Transcode") + { + if ((val.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == 0) + { + Assert.All( + videoStreams, + stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs)); + } + + // todo: fill out tests here + } + + // DirectStream and Remux + else + { + // check expected video codec (1) + Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); + Assert.Single(val.TargetVideoCodec); + + if (transcodeMode == "DirectStream") + { + if (!targetAudioStream.IsExternal) + { + // check expected audio codecs (1) + Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs); + } + } + else if (transcodeMode == "Remux") + { + // check expected audio codecs (1) + Assert.Contains(targetAudioStream.Codec, val.AudioCodecs); + Assert.Single(val.AudioCodecs); + } + + // video details + var videoStream = targetVideoStream; + Assert.False(val.EstimateContentLength); + Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); + Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, val.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty<string>()); + Assert.Equal(videoStream.Level, val.TargetVideoLevel); + Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth); + Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue); + + // audio codec not supported + if ((why & TranscodeReason.AudioCodecNotSupported) != 0) + { + // audio stream specified + if (options.AudioStreamIndex >= 0) + { + // TODO:fixme + if (!targetAudioStream.IsExternal) + { + Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs); + } + } + + // audio stream not specified + else + { + // TODO:fixme + Assert.All(audioStreams, stream => + { + if (!stream.IsExternal) + { + Assert.DoesNotContain(stream.Codec, val.AudioCodecs); + } + }); + } + } + } + } + else if (playMethod == null) + { + Assert.Null(val.SubProtocol); + Assert.Equal("stream", uri.Filename); + + Assert.False(val.EstimateContentLength); + Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); + } + + return val; + } + + private static async ValueTask<T> TestData<T>(string name) + { + var path = Path.Join("Test Data", typeof(T).Name + "-" + name + ".json"); + using (var stream = File.OpenRead(path)) + { + var value = await JsonSerializer.DeserializeAsync<T>(stream, JsonDefaults.Options); + if (value != null) + { + return value; + } + + throw new SerializationException("Invalid test data: " + name); + } + } + + private StreamBuilder GetStreamBuilder() + { + var transcodeSupport = new Mock<ITranscoderSupport>(); + var logger = new NullLogger<StreamBuilderTests>(); + + return new StreamBuilder(transcodeSupport.Object, logger); + } + + private static async ValueTask<VideoOptions> GetVideoOptions(string deviceProfile, params string[] sources) + { + var mediaSources = sources.Select(src => TestData<MediaSourceInfo>(src)) + .Select(val => val.Result) + .ToArray(); + var mediaSourceId = mediaSources[0]?.Id; + + var dp = await TestData<DeviceProfile>(deviceProfile); + + return new VideoOptions() + { + ItemId = new Guid("11D229B7-2D48-4B95-9F9B-49F6AB75E613"), + MediaSourceId = mediaSourceId, + MediaSources = mediaSources, + DeviceId = "test-deviceId", + Profile = dp, + AllowAudioStreamCopy = true, + AllowVideoStreamCopy = true, + }; + } + + private static (string Path, NameValueCollection Query, string Filename, string Extension) ParseUri(StreamInfo val) + { + var href = val.ToUrl("media:", "ACCESSTOKEN").Split("?", 2); + var path = href[0]; + + var queryString = href.ElementAtOrDefault(1); + var query = string.IsNullOrEmpty(queryString) ? System.Web.HttpUtility.ParseQueryString(queryString ?? string.Empty) : new NameValueCollection(); + + var filename = Path.GetFileNameWithoutExtension(path); + var extension = Path.GetExtension(path); + if (extension.Length > 0) + { + extension = extension.Substring(1); + } + + return (path, query, filename, extension); + } + } +} diff --git a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs index 0c97a90b4..9fcf8189f 100644 --- a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs +++ b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs @@ -5,23 +5,24 @@ namespace Jellyfin.Model.Tests.Entities { public class MediaStreamTests { - public static TheoryData<MediaStream, string> Get_DisplayTitle_TestData() + public static TheoryData<string, MediaStream> Get_DisplayTitle_TestData() { - var data = new TheoryData<MediaStream, string>(); + var data = new TheoryData<string, MediaStream>(); data.Add( - new MediaStream - { - Type = MediaStreamType.Subtitle, - Title = "English", - Language = string.Empty, - IsForced = false, - IsDefault = false, - Codec = "ASS" - }, - "English - Und - ASS"); + "English - Und - ASS", + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = "English", + Language = string.Empty, + IsForced = false, + IsDefault = false, + Codec = "ASS" + }); data.Add( + "English - Und", new MediaStream { Type = MediaStreamType.Subtitle, @@ -30,10 +31,10 @@ namespace Jellyfin.Model.Tests.Entities IsForced = false, IsDefault = false, Codec = string.Empty - }, - "English - Und"); + }); data.Add( + "English", new MediaStream { Type = MediaStreamType.Subtitle, @@ -42,10 +43,10 @@ namespace Jellyfin.Model.Tests.Entities IsForced = false, IsDefault = false, Codec = string.Empty - }, - "English"); + }); data.Add( + "English - Default - Forced - SRT", new MediaStream { Type = MediaStreamType.Subtitle, @@ -54,10 +55,23 @@ namespace Jellyfin.Model.Tests.Entities IsForced = true, IsDefault = true, Codec = "SRT" - }, - "English - Default - Forced - SRT"); + }); + + data.Add( + "Title - EN - Default - Forced - SRT - External", + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = "Title", + Language = "EN", + IsForced = true, + IsDefault = true, + Codec = "SRT", + IsExternal = true + }); data.Add( + "Und", new MediaStream { Type = MediaStreamType.Subtitle, @@ -66,15 +80,27 @@ namespace Jellyfin.Model.Tests.Entities IsForced = false, IsDefault = false, Codec = null - }, - "Und"); + }); + + data.Add( + "Title - AAC - Default - External", + new MediaStream + { + Type = MediaStreamType.Audio, + Title = "Title", + Language = null, + IsForced = false, + IsDefault = true, + Codec = "AAC", + IsExternal = true + }); return data; } [Theory] [MemberData(nameof(Get_DisplayTitle_TestData))] - public void Get_DisplayTitle_should_return_valid_title(MediaStream mediaStream, string expected) + public void Get_DisplayTitle_should_return_valid_title(string expected, MediaStream mediaStream) { Assert.Equal(expected, mediaStream.DisplayTitle); } diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index 22db5bea2..5fdb22546 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -7,13 +7,20 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="FsCheck.Xunit" Version="2.16.4" /> </ItemGroup> + <ItemGroup> + <None Include="Test Data\**\*.*"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + </ItemGroup> + <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> @@ -21,7 +28,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json new file mode 100644 index 000000000..68ce3ea4a --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json @@ -0,0 +1,332 @@ +{ + "Name": "Jellyfin Android", + "EnableAlbumArtInDidl": false, + "EnableSingleAlbumArtLimit": false, + "EnableSingleSubtitleLimit": false, + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxAlbumArtWidth": 2147483647, + "MaxAlbumArtHeight": 2147483647, + "MaxStreamingBitrate": 8000000, + "MaxStaticBitrate": 8000000, + "MusicStreamingTranscodingBitrate": 128000, + "TimelineOffsetSeconds": 0, + "RequiresPlainVideoItems": false, + "RequiresPlainFolders": false, + "EnableMSMediaReceiverRegistrar": false, + "IgnoreTranscodeByteRangeRequests": false, + "DirectPlayProfiles": [ + { + "Container": "mp4", + "AudioCodec": "mp3,aac,alac,ac3", + "VideoCodec": "h263,mpeg4,h264,hevc,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp4", + "AudioCodec": "mp3,aac,alac,ac3", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "fmp4", + "AudioCodec": "mp3,aac,ac3,eac3", + "VideoCodec": "h263,mpeg4,h264,hevc,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "fmp4", + "AudioCodec": "mp3,aac,ac3,eac3", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webm", + "AudioCodec": "vorbis,opus", + "VideoCodec": "vp8,vp9,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "webm", + "AudioCodec": "vorbis,opus", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "mkv", + "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp3,aac,vorbis,opus,flac,alac,ac3,eac3,dts,mlp,truehd", + "VideoCodec": "h263,mpeg4,h264,hevc,av1,vp8,vp9,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mkv", + "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp3,aac,vorbis,opus,flac,alac,ac3,eac3,dts,mlp,truehd", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp3", + "AudioCodec": "mp3", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "ogg", + "AudioCodec": "vorbis,opus,flac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "wav", + "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "mpegts", + "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp3,aac,ac3,eac3,dts,mlp,truehd", + "VideoCodec": "mpeg4,h264,hevc", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mpegts", + "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp3,aac,ac3,eac3,dts,mlp,truehd", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "flv", + "AudioCodec": "mp3,aac", + "VideoCodec": "mpeg4,h264", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "flv", + "AudioCodec": "mp3,aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "aac", + "AudioCodec": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "flac", + "AudioCodec": "flac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "3gp", + "AudioCodec": "3gpp,aac,flac", + "VideoCodec": "h263,mpeg4,h264,hevc", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "3gp", + "AudioCodec": "3gpp,aac,flac", + "Type": "Audio", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Container": "ts", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "mp1,mp2,mp3,aac,ac3,eac3,dts,mlp,truehd", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mkv", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp1,mp2,mp3,aac,vorbis,opus,flac,alac,ac3,eac3,dts,mlp,truehd", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "ContainerProfiles": [ + { + "Type": "Video", + "Container": "mp4", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "mp4", + "$type": "ContainerProfile" + }, + { + "Type": "Video", + "Container": "fmp4", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "fmp4", + "$type": "ContainerProfile" + }, + { + "Type": "Video", + "Container": "webm", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "webm", + "$type": "ContainerProfile" + }, + { + "Type": "Video", + "Container": "mkv", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "mkv", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "mp3", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "ogg", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "wav", + "$type": "ContainerProfile" + }, + { + "Type": "Video", + "Container": "mpegts", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "mpegts", + "$type": "ContainerProfile" + }, + { + "Type": "Video", + "Container": "flv", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "flv", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "aac", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "flac", + "$type": "ContainerProfile" + }, + { + "Type": "Video", + "Container": "3gp", + "$type": "ContainerProfile" + }, + { + "Type": "Audio", + "Container": "3gp", + "$type": "ContainerProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "srt", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "subrip", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ttml", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "subrip", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ttml", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "vtt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "webvtt", + "Method": "External", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json new file mode 100644 index 000000000..5d1f5f162 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json @@ -0,0 +1,430 @@ +{ + "EnableAlbumArtInDidl": false, + "EnableSingleAlbumArtLimit": false, + "EnableSingleSubtitleLimit": false, + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxAlbumArtWidth": 0, + "MaxAlbumArtHeight": 0, + "MaxStreamingBitrate": 120000000, + "MaxStaticBitrate": 100000000, + "MusicStreamingTranscodingBitrate": 384000, + "TimelineOffsetSeconds": 0, + "RequiresPlainVideoItems": false, + "RequiresPlainFolders": false, + "EnableMSMediaReceiverRegistrar": false, + "IgnoreTranscodeByteRangeRequests": false, + "DirectPlayProfiles": [ + { + "Container": "webm", + "AudioCodec": "vorbis,opus", + "VideoCodec": "vp8,vp9,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp4,m4v", + "AudioCodec": "aac,mp3,opus,flac,alac,vorbis", + "VideoCodec": "h264,vp8,vp9,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mov", + "AudioCodec": "aac,mp3,opus,flac,alac,vorbis", + "VideoCodec": "h264", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "opus", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webm", + "AudioCodec": "opus", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4a", + "AudioCodec": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4b", + "AudioCodec": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "flac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4a", + "AudioCodec": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4b", + "AudioCodec": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webma", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webm", + "AudioCodec": "webma", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "ogg", + "Type": "Audio", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "opus", + "Type": "Audio", + "AudioCodec": "opus", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "AudioCodec": "wav", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "opus", + "Type": "Audio", + "AudioCodec": "opus", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "AudioCodec": "wav", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp4", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "aac,mp3,opus,flac,alac,vorbis", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "webm", + "Type": "Video", + "VideoCodec": "vp8,vp9,av1,vpx", + "AudioCodec": "vorbis,opus", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp4", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "aac,mp3,opus,flac,alac,vorbis", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "CodecProfiles": [ + { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "aac", + "$type": "CodecProfile" + }, + { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "$type": "CodecProfile" + }, + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "high|main|baseline|constrained baseline|high 10", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "52", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "h264", + "$type": "CodecProfile" + }, + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "main", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "120", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "hevc", + "$type": "CodecProfile" + } + ], + "ResponseProfiles": [ + { + "Container": "m4v", + "Type": "Video", + "MimeType": "video/mp4", + "$type": "ResponseProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "vtt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "External", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json new file mode 100644 index 000000000..81bb97ac8 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json @@ -0,0 +1,448 @@ +{ + "EnableAlbumArtInDidl": false, + "EnableSingleAlbumArtLimit": false, + "EnableSingleSubtitleLimit": false, + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxAlbumArtWidth": 0, + "MaxAlbumArtHeight": 0, + "MaxStreamingBitrate": 120000000, + "MaxStaticBitrate": 100000000, + "MusicStreamingTranscodingBitrate": 384000, + "TimelineOffsetSeconds": 0, + "RequiresPlainVideoItems": false, + "RequiresPlainFolders": false, + "EnableMSMediaReceiverRegistrar": false, + "IgnoreTranscodeByteRangeRequests": false, + "DirectPlayProfiles": [ + { + "Container": "webm", + "AudioCodec": "vorbis,opus", + "VideoCodec": "vp8,vp9,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp4,m4v", + "AudioCodec": "aac,mp3,opus,flac,alac,vorbis", + "VideoCodec": "h264,vp8,vp9,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mov", + "AudioCodec": "aac,mp3,opus,flac,alac,vorbis", + "VideoCodec": "h264", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "opus", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webm", + "AudioCodec": "opus", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4a", + "AudioCodec": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4b", + "AudioCodec": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "flac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4a", + "AudioCodec": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4b", + "AudioCodec": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webma", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webm", + "AudioCodec": "webma", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "ogg", + "Type": "Audio", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Container": "ts", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 2, + "SegmentLength": 0, + "BreakOnNonKeyFrames": true, + "$type": "TranscodingProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "opus", + "Type": "Audio", + "AudioCodec": "opus", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "AudioCodec": "wav", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "opus", + "Type": "Audio", + "AudioCodec": "opus", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "AudioCodec": "wav", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "ts", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "aac,mp3", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 2, + "SegmentLength": 0, + "BreakOnNonKeyFrames": true, + "$type": "TranscodingProfile" + }, + { + "Container": "webm", + "Type": "Video", + "VideoCodec": "vp8,vp9,av1,vpx", + "AudioCodec": "vorbis,opus", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp4", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "aac,mp3,opus,flac,alac,vorbis", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "CodecProfiles": [ + { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "aac", + "$type": "CodecProfile" + }, + { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "$type": "CodecProfile" + }, + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "high|main|baseline|constrained baseline|high 10", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "52", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "h264", + "$type": "CodecProfile" + }, + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "main", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "120", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "hevc", + "$type": "CodecProfile" + } + ], + "ResponseProfiles": [ + { + "Container": "m4v", + "Type": "Video", + "MimeType": "video/mp4", + "$type": "ResponseProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "vtt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "External", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-DirectMedia.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-DirectMedia.json new file mode 100644 index 000000000..d1df7341e --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-DirectMedia.json @@ -0,0 +1,90 @@ +{ + "Name": "Jellyfin Media Player", + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxStreamingBitrate": 20000000, + "MaxStaticBitrate": 20000000, + "MusicStreamingTranscodingBitrate": 1280000, + "TimelineOffsetSeconds": 5, + "DirectPlayProfiles": [ + { + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Type": "Photo", + "$type": "DirectPlayProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "srt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "pgssub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "dvdsub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "pgs", + "Method": "Embed", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json new file mode 100644 index 000000000..9874793d3 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json @@ -0,0 +1,441 @@ +{ + "EnableAlbumArtInDidl": false, + "EnableSingleAlbumArtLimit": false, + "EnableSingleSubtitleLimit": false, + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxAlbumArtWidth": 0, + "MaxAlbumArtHeight": 0, + "MaxStreamingBitrate": 120000000, + "MaxStaticBitrate": 100000000, + "MusicStreamingTranscodingBitrate": 384000, + "TimelineOffsetSeconds": 0, + "RequiresPlainVideoItems": false, + "RequiresPlainFolders": false, + "EnableMSMediaReceiverRegistrar": false, + "IgnoreTranscodeByteRangeRequests": false, + "DirectPlayProfiles": [ + { + "Container": "webm", + "AudioCodec": "vorbis,opus", + "VideoCodec": "vp8,vp9,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp4,m4v", + "AudioCodec": "aac,mp3,opus,flac,alac,vorbis", + "VideoCodec": "h264,vp8,vp9,av1", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "opus", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webm", + "AudioCodec": "opus", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4a", + "AudioCodec": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4b", + "AudioCodec": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "flac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4a", + "AudioCodec": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4b", + "AudioCodec": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webma", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webm", + "AudioCodec": "webma", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "ogg", + "Type": "Audio", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Container": "ts", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 2, + "SegmentLength": 0, + "BreakOnNonKeyFrames": true, + "$type": "TranscodingProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "opus", + "Type": "Audio", + "AudioCodec": "opus", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "AudioCodec": "wav", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "opus", + "Type": "Audio", + "AudioCodec": "opus", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "AudioCodec": "wav", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "ts", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "aac,mp3", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 2, + "SegmentLength": 0, + "BreakOnNonKeyFrames": true, + "$type": "TranscodingProfile" + }, + { + "Container": "webm", + "Type": "Video", + "VideoCodec": "vp8,vp9,av1,vpx", + "AudioCodec": "vorbis,opus", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp4", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "aac,mp3,opus,flac,alac,vorbis", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "CodecProfiles": [ + { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "aac", + "$type": "CodecProfile" + }, + { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "$type": "CodecProfile" + }, + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "high|main|baseline|constrained baseline", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "52", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "h264", + "$type": "CodecProfile" + }, + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "main", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "120", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "hevc", + "$type": "CodecProfile" + } + ], + "ResponseProfiles": [ + { + "Container": "m4v", + "Type": "Video", + "MimeType": "video/mp4", + "$type": "ResponseProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "vtt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "External", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json new file mode 100644 index 000000000..da9a1a4ad --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json @@ -0,0 +1,137 @@ +{ + "Name": "Jellyfin Media Player", + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxStreamingBitrate": 8000000, + "MaxStaticBitrate": 8000000, + "MusicStreamingTranscodingBitrate": 1280000, + "TimelineOffsetSeconds": 5, + "DirectPlayProfiles": [ + { + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Type": "Photo", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Type": "Audio", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "ts", + "Type": "Video", + "VideoCodec": "h264,h265,hevc,mpeg4,mpeg2video", + "AudioCodec": "aac,mp3,ac3,opus,flac,vorbis", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "jpeg", + "Type": "Photo", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "srt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "pgssub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "dvdsub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "pgs", + "Method": "Embed", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json new file mode 100644 index 000000000..82b73fb0f --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json @@ -0,0 +1,137 @@ +{ + "Name": "Jellyfin Media Player", + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxStreamingBitrate": 120000, + "MaxStaticBitrate": 100000, + "MusicStreamingTranscodingBitrate": 3840, + "TimelineOffsetSeconds": 5, + "DirectPlayProfiles": [ + { + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Type": "Photo", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Type": "Audio", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "ts", + "Type": "Video", + "VideoCodec": "h264,h265,hevc,mpeg4,mpeg2video", + "AudioCodec": "aac,mp3,ac3,opus,flac,vorbis", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "jpeg", + "Type": "Photo", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "srt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "pgssub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "dvdsub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "pgs", + "Method": "Embed", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Null.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Null.json new file mode 100644 index 000000000..d463bd896 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Null.json @@ -0,0 +1,9 @@ +{ + "Name": "Jellyfin Media Player", + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxStreamingBitrate": 120000, + "MaxStaticBitrate": 100000, + "MusicStreamingTranscodingBitrate": 3840, + "TimelineOffsetSeconds": 5, + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json new file mode 100644 index 000000000..37b923558 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json @@ -0,0 +1,211 @@ +{ + "EnableAlbumArtInDidl": false, + "EnableSingleAlbumArtLimit": false, + "EnableSingleSubtitleLimit": false, + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxAlbumArtWidth": 0, + "MaxAlbumArtHeight": 0, + "MaxStreamingBitrate": 120000000, + "MaxStaticBitrate": 100000000, + "MusicStreamingTranscodingBitrate": 192000, + "TimelineOffsetSeconds": 0, + "RequiresPlainVideoItems": false, + "RequiresPlainFolders": false, + "EnableMSMediaReceiverRegistrar": false, + "IgnoreTranscodeByteRangeRequests": false, + "DirectPlayProfiles": [ + { + "Container": "mp4,m4v,mov", + "AudioCodec": "mp3,pcm,lpcm,wav,alac,aac", + "VideoCodec": "h264,h265,hevc,mpeg2video", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mkv,webm", + "AudioCodec": "mp3,pcm,lpcm,wav,flac,alac,aac,opus,vorbis", + "VideoCodec": "h264,vp8,h265,hevc,vp9,mpeg2video", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp3,pcm,lpcm,wav,wma,flac,alac,aac,wmapro", + "Type": "Audio", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": " 2", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "2", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "2", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": " 2", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "ts", + "Type": "Video", + "VideoCodec": "h264,mpeg2video", + "AudioCodec": "aac", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": " 2", + "MinSegments": 1, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp4", + "Type": "Video", + "VideoCodec": "h264,h265,hevc,mpeg2video", + "AudioCodec": "mp3,pcm,lpcm,wav,alac,aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "CodecProfiles": [ + { + "Type": "Video", + "Conditions": [ + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "high|main|baseline|constrained baseline", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "51", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "h264", + "$type": "CodecProfile" + }, + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "main|main 10", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "hevc", + "$type": "CodecProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "vtt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ttml", + "Method": "External", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json new file mode 100644 index 000000000..542bf6370 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json @@ -0,0 +1,211 @@ +{ + "EnableAlbumArtInDidl": false, + "EnableSingleAlbumArtLimit": false, + "EnableSingleSubtitleLimit": false, + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxAlbumArtWidth": 0, + "MaxAlbumArtHeight": 0, + "MaxStreamingBitrate": 120000000, + "MaxStaticBitrate": 100000000, + "MusicStreamingTranscodingBitrate": 192000, + "TimelineOffsetSeconds": 0, + "RequiresPlainVideoItems": false, + "RequiresPlainFolders": false, + "EnableMSMediaReceiverRegistrar": false, + "IgnoreTranscodeByteRangeRequests": false, + "DirectPlayProfiles": [ + { + "Container": "mp4,m4v,mov", + "AudioCodec": "mp3,pcm,lpcm,wav,alac,aac", + "VideoCodec": "h264,h265,hevc,mpeg2video", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mkv,webm", + "AudioCodec": "mp3,pcm,lpcm,wav,flac,alac,aac,opus,vorbis", + "VideoCodec": "h264,vp8,h265,hevc,vp9,mpeg2video", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp3,pcm,lpcm,wav,wma,flac,alac,aac,wmapro", + "Type": "Audio", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": " 2", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "2", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "2", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": " 2", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "ts", + "Type": "Video", + "VideoCodec": "h264,h265,hevc,mpeg2video", + "AudioCodec": "aac", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": " 2", + "MinSegments": 1, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp4", + "Type": "Video", + "VideoCodec": "h264,h265,hevc,mpeg2video", + "AudioCodec": "mp3,pcm,lpcm,wav,alac,aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "CodecProfiles": [ + { + "Type": "Video", + "Conditions": [ + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "high|main|baseline|constrained baseline", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "51", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "h264", + "$type": "CodecProfile" + }, + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "main|main 10", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "hevc", + "$type": "CodecProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "vtt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ttml", + "Method": "External", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json new file mode 100644 index 000000000..3b5a0c254 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json @@ -0,0 +1,357 @@ +{ + "EnableAlbumArtInDidl": false, + "EnableSingleAlbumArtLimit": false, + "EnableSingleSubtitleLimit": false, + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxAlbumArtWidth": 0, + "MaxAlbumArtHeight": 0, + "MaxStreamingBitrate": 120000000, + "MaxStaticBitrate": 100000000, + "MusicStreamingTranscodingBitrate": 384000, + "TimelineOffsetSeconds": 0, + "RequiresPlainVideoItems": false, + "RequiresPlainFolders": false, + "EnableMSMediaReceiverRegistrar": false, + "IgnoreTranscodeByteRangeRequests": false, + "DirectPlayProfiles": [ + { + "Container": "webm", + "AudioCodec": "vorbis", + "VideoCodec": "vp8,vp9", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp4,m4v", + "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis", + "VideoCodec": "h264,vp8,vp9", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mov", + "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis", + "VideoCodec": "h264", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4a", + "AudioCodec": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4b", + "AudioCodec": "aac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "flac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4a", + "AudioCodec": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "m4b", + "AudioCodec": "alac", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webma", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "webm", + "AudioCodec": "webma", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 2, + "SegmentLength": 0, + "BreakOnNonKeyFrames": true, + "$type": "TranscodingProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "AudioCodec": "wav", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "aac", + "Type": "Audio", + "AudioCodec": "aac", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "wav", + "Type": "Audio", + "AudioCodec": "wav", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp4", + "Type": "Video", + "AudioCodec": "aac,ac3,eac3,flac,alac", + "VideoCodec": "hevc,h264", + "Context": "Streaming", + "Protocol": "hls", + "MaxAudioChannels": "2", + "MinSegments": "2", + "BreakOnNonKeyFrames": true + }, + { + "Container": "ts", + "Type": "Video", + "AudioCodec": "aac,mp3,ac3,eac3", + "VideoCodec": "h264", + "Context": "Streaming", + "Protocol": "hls", + "MaxAudioChannels": "2", + "MinSegments": "2", + "BreakOnNonKeyFrames": true + }, + { + "Container": "webm", + "Type": "Video", + "AudioCodec": "vorbis", + "VideoCodec": "vp8,vpx", + "Context": "Streaming", + "Protocol": "http", + "MaxAudioChannels": "2" + }, + { + "Container": "mp4", + "Type": "Video", + "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis", + "VideoCodec": "h264", + "Context": "Static", + "Protocol": "http" + } + ], + "CodecProfiles": [ + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "high|main|baseline|constrained baseline", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "52", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "h264", + "$type": "CodecProfile" + }, + { + "Type": "Video", + "Conditions": [ + { + "Condition": "NotEquals", + "Property": "IsAnamorphic", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoProfile", + "Value": "main|main 10", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "LessThanEqual", + "Property": "VideoLevel", + "Value": "183", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "NotEquals", + "Property": "IsInterlaced", + "Value": "true", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "hevc", + "$type": "CodecProfile" + } + ], + "ResponseProfiles": [ + { + "Container": "m4v", + "Type": "Video", + "MimeType": "video/mp4", + "$type": "ResponseProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "vtt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "External", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json new file mode 100644 index 000000000..9fc1ae6bb --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json @@ -0,0 +1,139 @@ +{ + "Name": "Jellyfin Media Player", + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxStreamingBitrate": 20000000, + "MaxStaticBitrate": 20000000, + "MusicStreamingTranscodingBitrate": 1280000, + "TimelineOffsetSeconds": 5, + "TranscodingProfiles": [ + { + "Type": "Audio", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp4", + "Type": "Video", + "AudioCodec": "aac,flac,alac", + "VideoCodec": "hevc,h264", + "Context": "Streaming", + "Protocol": "hls", + "MaxAudioChannels": "2", + "MinSegments": "2", + "BreakOnNonKeyFrames": true, + "$type": "TranscodingProfile" + }, + { + "Container": "ts", + "Type": "Video", + "AudioCodec": "aac,mp3", + "VideoCodec": "h264", + "Context": "Streaming", + "Protocol": "hls", + "MaxAudioChannels": "2", + "MinSegments": "2", + "BreakOnNonKeyFrames": true, + "$type": "TranscodingProfile" + }, + { + "Container": "webm", + "Type": "Video", + "AudioCodec": "vorbis", + "VideoCodec": "vp9,vp8,vpx,av1", + "Context": "Streaming", + "Protocol": "http", + "MaxAudioChannels": "2", + "$type": "TranscodingProfile" + }, + { + "Container": "jpeg", + "Type": "Photo", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "srt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "pgssub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "dvdsub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "pgs", + "Method": "Embed", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json new file mode 100644 index 000000000..256c8dc2f --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json @@ -0,0 +1,189 @@ +{ + "EnableAlbumArtInDidl": false, + "EnableSingleAlbumArtLimit": false, + "EnableSingleSubtitleLimit": false, + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxAlbumArtWidth": 0, + "MaxAlbumArtHeight": 0, + "MaxStreamingBitrate": 120000000, + "MaxStaticBitrate": 100000000, + "MusicStreamingTranscodingBitrate": 192000, + "TimelineOffsetSeconds": 0, + "RequiresPlainVideoItems": false, + "RequiresPlainFolders": false, + "EnableMSMediaReceiverRegistrar": false, + "IgnoreTranscodeByteRangeRequests": false, + "DirectPlayProfiles": [ + { + "Container": "", + "AudioCodec": "aac", + "VideoCodec": "", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "ts,mp4,mka,m4a,mp3,mp2,wav,flac,ogg", + "AudioCodec": "", + "VideoCodec": "", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "", + "AudioCodec": "", + "VideoCodec": "", + "Type": "Photo", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Container": "ts", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "aac", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": true, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "VideoCodec": "", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "VideoCodec": "", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "CodecProfiles": [ + { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "", + "Container": "", + "$type": "CodecProfile" + } + ], + "ResponseProfiles": [ + { + "Container": "m4v", + "Type": "Video", + "MimeType": "video/mp4", + "$type": "ResponseProfile" + }, + { + "Container": "mov", + "Type": "Video", + "MimeType": "video/webm", + "$type": "ResponseProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "vtt", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "subrip", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "dvdsub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "pgs", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "pgssub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "External", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json new file mode 100644 index 000000000..256c8dc2f --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json @@ -0,0 +1,189 @@ +{ + "EnableAlbumArtInDidl": false, + "EnableSingleAlbumArtLimit": false, + "EnableSingleSubtitleLimit": false, + "SupportedMediaTypes": "Audio,Photo,Video", + "MaxAlbumArtWidth": 0, + "MaxAlbumArtHeight": 0, + "MaxStreamingBitrate": 120000000, + "MaxStaticBitrate": 100000000, + "MusicStreamingTranscodingBitrate": 192000, + "TimelineOffsetSeconds": 0, + "RequiresPlainVideoItems": false, + "RequiresPlainFolders": false, + "EnableMSMediaReceiverRegistrar": false, + "IgnoreTranscodeByteRangeRequests": false, + "DirectPlayProfiles": [ + { + "Container": "", + "AudioCodec": "aac", + "VideoCodec": "", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "ts,mp4,mka,m4a,mp3,mp2,wav,flac,ogg", + "AudioCodec": "", + "VideoCodec": "", + "Type": "Audio", + "$type": "DirectPlayProfile" + }, + { + "Container": "", + "AudioCodec": "", + "VideoCodec": "", + "Type": "Photo", + "$type": "DirectPlayProfile" + } + ], + "TranscodingProfiles": [ + { + "Container": "ts", + "Type": "Video", + "VideoCodec": "h264", + "AudioCodec": "aac", + "Protocol": "hls", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": true, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "VideoCodec": "", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Static", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + }, + { + "Container": "mp3", + "Type": "Audio", + "VideoCodec": "", + "AudioCodec": "mp3", + "Protocol": "http", + "EstimateContentLength": false, + "EnableMpegtsM2TsMode": false, + "TranscodeSeekInfo": "Auto", + "CopyTimestamps": false, + "Context": "Streaming", + "EnableSubtitlesInManifest": false, + "MaxAudioChannels": "6", + "MinSegments": 0, + "SegmentLength": 0, + "BreakOnNonKeyFrames": false, + "$type": "TranscodingProfile" + } + ], + "CodecProfiles": [ + { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "Codec": "", + "Container": "", + "$type": "CodecProfile" + } + ], + "ResponseProfiles": [ + { + "Container": "m4v", + "Type": "Video", + "MimeType": "video/mp4", + "$type": "ResponseProfile" + }, + { + "Container": "mov", + "Type": "Video", + "MimeType": "video/webm", + "$type": "ResponseProfile" + } + ], + "SubtitleProfiles": [ + { + "Format": "vtt", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ass", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "ssa", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "smi", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "subrip", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "dvdsub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "pgs", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "pgssub", + "Method": "Embed", + "$type": "SubtitleProfile" + }, + { + "Format": "srt", + "Method": "External", + "$type": "SubtitleProfile" + }, + { + "Format": "sub", + "Method": "External", + "$type": "SubtitleProfile" + } + ], + "$type": "DeviceProfile" +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json new file mode 100644 index 000000000..da185aacf --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json @@ -0,0 +1,73 @@ +{ + "Id": "a766d122b58e45d9492d17af66748bf5", + "Path": "/Media/MyVideo-720p.mkv", + "Container": "mkv,webm", + "Size": 835317696, + "Name": "MyVideo-1080p", + "ETag": "579a34c6d5dfb23f61539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "av1", + "Language": "eng", + "ColorTransfer": "bt709", + "ColorPrimaries": "bt709", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "1080p AV1 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Main", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": -99 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 1, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json new file mode 100644 index 000000000..774dba32a --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json @@ -0,0 +1,72 @@ +{ + "Id": "a766d122b58e45d9492d17af66748bf5", + "Path": "/Media/MyVideo-720p.mkv", + "Container": "mkv,webm", + "Size": 835317696, + "Name": "MyVideo-1080p", + "ETag": "579a34c6d5dfb23f61539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "av1", + "Language": "eng", + "ColorTransfer": "bt709", + "ColorPrimaries": "bt709", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "1080p AV1 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Main", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": -99 + }, + { + "Codec": "vorbis", + "CodecTag": "ogg", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Vorbis - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json new file mode 100644 index 000000000..0a85a1353 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json @@ -0,0 +1,73 @@ +{ + "Id": "a766d122b58e45d9492d17af66748bf5", + "Path": "/Media/MyVideo-720p.mkv", + "Container": "mkv,webm", + "Size": 835317696, + "Name": "MyVideo-1080p", + "ETag": "579a34c6d5dfb23f61539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "vp9", + "Language": "eng", + "ColorTransfer": "bt709", + "ColorPrimaries": "bt709", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "1080p VP9 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Profile 0", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": -99 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 1, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json new file mode 100644 index 000000000..2b932ff52 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json @@ -0,0 +1,72 @@ +{ + "Id": "a766d122b58e45d9492d17af66748bf5", + "Path": "/Media/MyVideo-720p.mkv", + "Container": "mkv,webm", + "Size": 835317696, + "Name": "MyVideo-1080p", + "ETag": "579a34c6d5dfb23f61539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "vp9", + "Language": "eng", + "ColorTransfer": "bt709", + "ColorPrimaries": "bt709", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "1080p VP9 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Profile 0", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": -99 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json new file mode 100644 index 000000000..56b04b789 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json @@ -0,0 +1,73 @@ +{ + "Id": "a766d122b58e45d9492d17af66748bf5", + "Path": "/Media/MyVideo-720p.mkv", + "Container": "mkv,webm", + "Size": 835317696, + "Name": "MyVideo-1080p", + "ETag": "579a34c6d5dfb23f61539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "vp9", + "Language": "eng", + "ColorTransfer": "bt709", + "ColorPrimaries": "bt709", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "1080p VP9 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Profile 0", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": -99 + }, + { + "Codec": "vorbis", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Vorbis - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 1, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "RequiredHttpHeaders": {}, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json new file mode 100644 index 000000000..1ee7eade9 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json @@ -0,0 +1,72 @@ +{ + "Id": "a766d122b58e45d9492d17af66748bf5", + "Path": "/Media/MyVideo-720p.mkv", + "Container": "mkv,webm", + "Size": 835317696, + "Name": "MyVideo-1080p", + "ETag": "579a34c6d5dfb23f61539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "vp9", + "Language": "eng", + "ColorTransfer": "bt709", + "ColorPrimaries": "bt709", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "1080p VP9 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Profile 0", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": -99 + }, + { + "Codec": "vorbis", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Vorbis - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 1, + "Score": 203 + }, + { + "Codec": "webvtt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json new file mode 100644 index 000000000..21911843d --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json @@ -0,0 +1,72 @@ +{ + "Id": "a766d122b58e45d9492d17af77748bf5", + "Path": "/Media/MyVideo-720p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 835317696, + "Name": "MyVideo-720p", + "ETag": "579a34c6d5dfb21d81539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "h264", + "CodecTag": "avc1", + "Language": "eng", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "720p H264 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "High", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": 41 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 1, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json new file mode 100644 index 000000000..77954a31a --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json @@ -0,0 +1,72 @@ +{ + "Id": "a766d122b58e45d9492d17af77748bf5", + "Path": "/Media/MyVideo-720p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 835317696, + "Name": "MyVideo-720p", + "ETag": "579a34c6d5dfb21d81539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "h264", + "CodecTag": "avc1", + "Language": "eng", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "720p H264 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "High", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": 41 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 1, + "Score": 203 + }, + { + "Codec": "webvtt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json new file mode 100644 index 000000000..70bbb9d0d --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json @@ -0,0 +1,87 @@ +{ + "Id": "a766d122b58e45d9492d17af77748bf5", + "Path": "/Media/MyVideo-720p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 835317696, + "Name": "MyVideo-720p", + "ETag": "579a34c6d5dfb21d81539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "h264", + "CodecTag": "avc1", + "Language": "eng", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "720p H264 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "High", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": 41 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 2, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 3, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 3 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json new file mode 100644 index 000000000..036e41f07 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json @@ -0,0 +1,87 @@ +{ + "Id": "a766d122b58e45d9492d17af77748bf5", + "Path": "/Media/MyVideo-720p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 835317696, + "Name": "MyVideo-720p", + "ETag": "579a34c6d5dfb21d81539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "h264", + "CodecTag": "avc1", + "Language": "eng", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "720p H264 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "High", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": 41 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 2, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 3, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 2, + "DefaultSubtitleStreamIndex": 3 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json new file mode 100644 index 000000000..b81c4597f --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json @@ -0,0 +1,89 @@ +{ + "Id": "a766d122b58e45d9492d17af77748bf5", + "Path": "/Media/MyVideo-720p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 835317696, + "Name": "MyVideo-720p", + "ETag": "579a34c6d5dfb21d81539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "h264", + "CodecTag": "avc1", + "Language": "eng", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "720p H264 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "High", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": 41 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "IsExternal": true, + "Profile": "LC", + "Index": 2, + "Score": 203, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 3, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 3 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json new file mode 100644 index 000000000..b71fd4a6a --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json @@ -0,0 +1,71 @@ +{ + "Id": "a766d122b58e45d9492d17af77748bf5", + "Path": "/Media/MyVideo-720p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 835317696, + "Name": "MyVideo-720p", + "ETag": "579a34c6d5dfb21d81539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "h264", + "CodecTag": "avc1", + "Language": "eng", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "720p H264 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "High", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": 41 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json new file mode 100644 index 000000000..4c6409e7b --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json @@ -0,0 +1,75 @@ +{ + "Id": "f6eab7118618ab26e61e495a1853481a", + "Path": "/Media/MyVideo-WEBDL-2160p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 6521110016, + "Name": "MyVideo WEBDL-2160p", + "ETag": "a2fb84b618ba2467fe377543f879e9bf", + "RunTimeTicks": 34318510080, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "hevc", + "CodecTag": "hev1", + "Language": "eng", + "ColorSpace": "bt2020nc", + "ColorTransfer": "smpte2084", + "ColorPrimaries": "bt2020", + "TimeBase": "1/16000", + "VideoRange": "HDR", + "DisplayTitle": "4K HEVC HDR", + "BitRate": 14715079, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 2160, + "Width": 3840, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Main 10", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p10le", + "Level": 150 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 1, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + } + ], + "Bitrate": 15201382, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json new file mode 100644 index 000000000..385bb7260 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json @@ -0,0 +1,89 @@ +{ + "Id": "f6eab7118618ab26e61e495a1853481a", + "Path": "/Media/MyVideo-WEBDL-2160p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 6521110016, + "Name": "MyVideo WEBDL-2160p", + "ETag": "a2fb84b618ba2467fe377543f879e9bf", + "RunTimeTicks": 34318510080, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "hevc", + "CodecTag": "hev1", + "Language": "eng", + "ColorSpace": "bt2020nc", + "ColorTransfer": "smpte2084", + "ColorPrimaries": "bt2020", + "TimeBase": "1/16000", + "VideoRange": "HDR", + "DisplayTitle": "4K HEVC HDR", + "BitRate": 14715079, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 2160, + "Width": 3840, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Main 10", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p10le", + "Level": 150 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 2, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 3, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + } + ], + "Bitrate": 15201382, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 3 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json new file mode 100644 index 000000000..fd1950bde --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json @@ -0,0 +1,91 @@ +{ + "Id": "f6eab7118618ab26e61e495a1853481a", + "Path": "/Media/MyVideo-WEBDL-2160p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 6521110016, + "Name": "MyVideo WEBDL-2160p", + "ETag": "a2fb84b618ba2467fe377543f879e9bf", + "RunTimeTicks": 34318510080, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "hevc", + "CodecTag": "hev1", + "Language": "eng", + "ColorSpace": "bt2020nc", + "ColorTransfer": "smpte2084", + "ColorPrimaries": "bt2020", + "TimeBase": "1/16000", + "VideoRange": "HDR", + "DisplayTitle": "4K HEVC HDR", + "BitRate": 14715079, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 2160, + "Width": 3840, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Main 10", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p10le", + "Level": 150 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "IsExternal": true, + "Profile": "LC", + "Index": 2, + "Score": 203, + "Path": "/Media/MyVideo-WEBDL-2160p.eng.m4a" + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 3, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + } + ], + "Bitrate": 15201382, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 3 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json new file mode 100644 index 000000000..dde7c15ea --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json @@ -0,0 +1,74 @@ +{ + "Id": "f6eab7118618ab26e61e495a1853481a", + "Path": "/Media/MyVideo-WEBDL-2160p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 6521110016, + "Name": "MyVideo WEBDL-2160p", + "ETag": "a2fb84b618ba2467fe377543f879e9bf", + "RunTimeTicks": 34318510080, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "hevc", + "CodecTag": "hev1", + "Language": "eng", + "ColorSpace": "bt2020nc", + "ColorTransfer": "smpte2084", + "ColorPrimaries": "bt2020", + "TimeBase": "1/16000", + "VideoRange": "HDR", + "DisplayTitle": "4K HEVC HDR", + "BitRate": 14715079, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 2160, + "Width": 3840, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "Main 10", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p10le", + "Level": 150 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 2, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + } + ], + "Bitrate": 15201382, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-raw.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-raw.json new file mode 100644 index 000000000..9ea55b805 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-raw.json @@ -0,0 +1,102 @@ +{ + "Id": "a766d122b58e45d9492d17af77748bf5", + "Path": "/Media/MyVideo-720p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 835317696, + "Name": "MyVideo-720p", + "ETag": "579a34c6d5dfb21d81539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "h264", + "CodecTag": "avc1", + "Language": "eng", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "720p H264 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "High", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": 41 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 2, + "Score": 203 + }, + { + "Codec": "mov_text", + "CodecTag": "tx3g", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 3, + "Score": 6421, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "IsDefault": true, + "Type": 2, + "Index": 4, + "Score": 6422, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + } + ], + "Bitrate": 2590008, + "RequiredHttpHeaders": {}, + "DefaultSubtitleStreamIndex": 1 +} diff --git a/tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs b/tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs new file mode 100644 index 000000000..b396b5440 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs @@ -0,0 +1,111 @@ +using System.Text.RegularExpressions; +using Emby.Naming.Common; +using Emby.Naming.ExternalFiles; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; +using Moq; +using Xunit; + +namespace Jellyfin.Naming.Tests.ExternalFiles; + +public class ExternalPathParserTests +{ + private readonly ExternalPathParser _audioPathParser; + private readonly ExternalPathParser _subtitlePathParser; + + public ExternalPathParserTests() + { + var englishCultureDto = new CultureDto("English", "English", "en", new[] { "eng" }); + var frenchCultureDto = new CultureDto("French", "French", "fr", new[] { "fre", "fra" }); + + var localizationManager = new Mock<ILocalizationManager>(MockBehavior.Loose); + localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"en.*", RegexOptions.IgnoreCase))) + .Returns(englishCultureDto); + localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"fr.*", RegexOptions.IgnoreCase))) + .Returns(frenchCultureDto); + + _audioPathParser = new ExternalPathParser(new NamingOptions(), localizationManager.Object, DlnaProfileType.Audio); + _subtitlePathParser = new ExternalPathParser(new NamingOptions(), localizationManager.Object, DlnaProfileType.Subtitle); + } + + [Theory] + [InlineData("")] + [InlineData("MyVideo.ass")] + [InlineData("MyVideo.mks")] + [InlineData("MyVideo.sami")] + [InlineData("MyVideo.srt")] + [InlineData("MyVideo.m4v")] + public void ParseFile_AudioExtensionsNotMatched_ReturnsNull(string path) + { + Assert.Null(_audioPathParser.ParseFile(path, string.Empty)); + } + + [Theory] + [InlineData("MyVideo.aa")] + [InlineData("MyVideo.aac")] + [InlineData("MyVideo.flac")] + [InlineData("MyVideo.m4a")] + [InlineData("MyVideo.mka")] + [InlineData("MyVideo.mp3")] + public void ParseFile_AudioExtensionsMatched_ReturnsPath(string path) + { + var actual = _audioPathParser.ParseFile(path, string.Empty); + Assert.NotNull(actual); + Assert.Equal(path, actual!.Path); + } + + [Theory] + [InlineData("")] + [InlineData("MyVideo.aa")] + [InlineData("MyVideo.aac")] + [InlineData("MyVideo.flac")] + [InlineData("MyVideo.mka")] + [InlineData("MyVideo.m4v")] + public void ParseFile_SubtitleExtensionsNotMatched_ReturnsNull(string path) + { + Assert.Null(_subtitlePathParser.ParseFile(path, string.Empty)); + } + + [Theory] + [InlineData("MyVideo.ass")] + [InlineData("MyVideo.mks")] + [InlineData("MyVideo.sami")] + [InlineData("MyVideo.srt")] + [InlineData("MyVideo.vtt")] + public void ParseFile_SubtitleExtensionsMatched_ReturnsPath(string path) + { + var actual = _subtitlePathParser.ParseFile(path, string.Empty); + Assert.NotNull(actual); + Assert.Equal(path, actual!.Path); + } + + [Theory] + [InlineData("", null, null)] + [InlineData(".default", null, null, true, false)] + [InlineData(".forced", null, null, false, true)] + [InlineData(".foreign", null, null, false, true)] + [InlineData(".default.forced", null, null, true, true)] + [InlineData(".forced.default", null, null, true, true)] + [InlineData(".DEFAULT.FORCED", null, null, true, true)] + [InlineData(".en", null, "eng")] + [InlineData(".EN", null, "eng")] + [InlineData(".fr.en", "fr", "eng")] + [InlineData(".en.fr", "en", "fre")] + [InlineData(".title.en.fr", "title.en", "fre")] + [InlineData(".Title Goes Here", "Title Goes Here", null)] + [InlineData(".Title.with.Separator", "Title.with.Separator", null)] + [InlineData(".title.en.default.forced", "title", "eng", true, true)] + [InlineData(".forced.default.en.title", "title", "eng", true, true)] + public void ParseFile_ExtraTokens_ParseToValues(string tokens, string? title, string? language, bool isDefault = false, bool isForced = false) + { + var path = "My.Video" + tokens + ".srt"; + + var actual = _subtitlePathParser.ParseFile(path, tokens); + + Assert.NotNull(actual); + Assert.Equal(title, actual!.Title); + Assert.Equal(language, actual.Language); + Assert.Equal(isDefault, actual.IsDefault); + Assert.Equal(isForced, actual.IsForced); + } +} diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 59b7e1cbe..69ba5d86a 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -12,7 +12,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> @@ -29,7 +30,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs deleted file mode 100644 index 2446660f3..000000000 --- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Emby.Naming.Common; -using Emby.Naming.Subtitles; -using Xunit; - -namespace Jellyfin.Naming.Tests.Subtitles -{ - public class SubtitleParserTests - { - private readonly NamingOptions _namingOptions = new NamingOptions(); - - [Theory] - [InlineData("The Skin I Live In (2011).srt", null, false, false)] - [InlineData("The Skin I Live In (2011).eng.srt", "eng", false, false)] - [InlineData("The Skin I Live In (2011).eng.default.srt", "eng", true, false)] - [InlineData("The Skin I Live In (2011).eng.forced.srt", "eng", false, true)] - [InlineData("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true)] - [InlineData("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true)] - [InlineData("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true)] - public void SubtitleParser_ValidFileName_Parses(string input, string language, bool isDefault, bool isForced) - { - var parser = new SubtitleParser(_namingOptions); - - var result = parser.ParseFile(input); - - Assert.Equal(language, result?.Language, true); - Assert.Equal(isDefault, result?.IsDefault); - Assert.Equal(isForced, result?.IsForced); - Assert.Equal(input, result?.Path); - } - - [Theory] - [InlineData("The Skin I Live In (2011).mp4")] - [InlineData("")] - public void SubtitleParser_InvalidFileName_ReturnsNull(string input) - { - var parser = new SubtitleParser(_namingOptions); - - Assert.Null(parser.ParseFile(input)); - } - } -} diff --git a/tests/Jellyfin.Networking.Tests/Configuration/NetworkConfigurationTests.cs b/tests/Jellyfin.Networking.Tests/Configuration/NetworkConfigurationTests.cs new file mode 100644 index 000000000..a78b872df --- /dev/null +++ b/tests/Jellyfin.Networking.Tests/Configuration/NetworkConfigurationTests.cs @@ -0,0 +1,28 @@ +using Jellyfin.Networking.Configuration; +using Xunit; + +namespace Jellyfin.Networking.Tests.Configuration; + +public static class NetworkConfigurationTests +{ + [Theory] + [InlineData("", null)] + [InlineData("", "")] + [InlineData("/Test", "/Test")] + [InlineData("/Test", "Test")] + [InlineData("/Test", "Test/")] + [InlineData("/Test", "/Test/")] + [InlineData("/Test/2", "/Test/2")] + [InlineData("/Test/2", "Test/2")] + [InlineData("/Test/2", "Test/2/")] + [InlineData("/Test/2", "/Test/2/")] + public static void BaseUrl_ReturnsNormalized(string expected, string input) + { + var config = new NetworkConfiguration() + { + BaseUrl = input + }; + + Assert.Equal(expected, config.BaseUrl); + } +} diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index 3d3288df6..ba39cc0ff 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -12,12 +12,12 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="FsCheck.Xunit" Version="2.16.4" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.2" /> </ItemGroup> <!-- Code Analyzers--> @@ -27,7 +27,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj index 9f571273f..6d52a3cd6 100644 --- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -13,8 +13,8 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> @@ -33,7 +33,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs new file mode 100644 index 000000000..aec523882 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Emby.Naming.Common; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Providers.MediaInfo; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo; + +public class AudioResolverTests +{ + private readonly AudioResolver _audioResolver; + + public AudioResolverTests() + { + // prep BaseItem and Video for calls made that expect managers + Video.LiveTvManager = Mock.Of<ILiveTvManager>(); + + var applicationPaths = new Mock<IServerApplicationPaths>().Object; + var serverConfig = new Mock<IServerConfigurationManager>(); + serverConfig.Setup(c => c.ApplicationPaths) + .Returns(applicationPaths); + BaseItem.ConfigurationManager = serverConfig.Object; + + // build resolver to test with + var localizationManager = Mock.Of<ILocalizationManager>(); + + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo + { + MediaStreams = new List<MediaStream> + { + new() + } + })); + + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex))) + .Returns(true); + + _audioResolver = new AudioResolver(localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + } + + [Theory] + [InlineData("My.Video.srt", false, false)] + [InlineData("My.Video.mp3", false, true)] + [InlineData("My.Video.srt", true, false)] + [InlineData("My.Video.mp3", true, true)] + public async void GetExternalStreams_MixedFilenames_PicksAudio(string file, bool metadataDirectory, bool matches) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = MediaInfoResolverTests.VideoDirectoryPath + "/My.Video.mkv" + }; + + var directoryService = MediaInfoResolverTests.GetDirectoryServiceForExternalFile(file, metadataDirectory); + var streams = await _audioResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None); + + if (matches) + { + Assert.Single(streams); + var actual = streams[0]; + Assert.Equal(MediaStreamType.Audio, actual.Type); + } + else + { + Assert.Empty(streams); + } + } +} diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs index 98ac1dd64..9b80f0b94 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -148,7 +148,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo var mediaSourceManager = new Mock<IMediaSourceManager>(MockBehavior.Strict); mediaSourceManager.Setup(i => i.GetMediaAttachments(item.Id)) .Returns(mediaAttachments); - mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId == item.Id && q.Type == MediaStreamType.EmbeddedImage))) + mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId.Equals(item.Id) && q.Type == MediaStreamType.EmbeddedImage))) .Returns(mediaStreams); return mediaSourceManager.Object; } diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs new file mode 100644 index 000000000..98b4a6ccf --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs @@ -0,0 +1,435 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Emby.Naming.Common; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Providers.MediaInfo; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo; + +public class MediaInfoResolverTests +{ + public const string VideoDirectoryPath = "Test Data/Video"; + public const string VideoDirectoryRegex = @"Test Data[/\\]Video"; + public const string MetadataDirectoryPath = "library/00/00000000000000000000000000000000"; + public const string MetadataDirectoryRegex = @"library.*"; + + private readonly ILocalizationManager _localizationManager; + private readonly MediaInfoResolver _subtitleResolver; + + public MediaInfoResolverTests() + { + // prep BaseItem and Video for calls made that expect managers + Video.LiveTvManager = Mock.Of<ILiveTvManager>(); + + var applicationPaths = new Mock<IServerApplicationPaths>().Object; + var serverConfig = new Mock<IServerConfigurationManager>(); + serverConfig.Setup(c => c.ApplicationPaths) + .Returns(applicationPaths); + BaseItem.ConfigurationManager = serverConfig.Object; + + // build resolver to test with + var englishCultureDto = new CultureDto("English", "English", "en", new[] { "eng" }); + + var localizationManager = new Mock<ILocalizationManager>(MockBehavior.Loose); + localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"en.*", RegexOptions.IgnoreCase))) + .Returns(englishCultureDto); + _localizationManager = localizationManager.Object; + + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo + { + MediaStreams = new List<MediaStream> + { + new() + } + })); + + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsAny<string>())) + .Returns(false); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex))) + .Returns(true); + + _subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + } + + [Fact] + public void GetExternalFiles_BadProtocol_ReturnsNoSubtitles() + { + // need a media source manager capable of returning something other than file protocol + var mediaSourceManager = new Mock<IMediaSourceManager>(); + mediaSourceManager.Setup(m => m.GetPathProtocol(It.IsRegex(@"http.*"))) + .Returns(MediaProtocol.Http); + BaseItem.MediaSourceManager = mediaSourceManager.Object; + + var video = new Movie + { + Path = "https://url.com/My.Video.mkv" + }; + + Assert.Empty(_subtitleResolver.GetExternalFiles(video, Mock.Of<IDirectoryService>(), false)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetExternalFiles_MissingDirectory_DirectoryNotQueried(bool metadataDirectory) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + string containingFolderPath, metadataPath; + + if (metadataDirectory) + { + containingFolderPath = VideoDirectoryPath; + metadataPath = "invalid"; + } + else + { + containingFolderPath = "invalid"; + metadataPath = MetadataDirectoryPath; + } + + var video = new Mock<Movie>(); + video.Setup(m => m.Path) + .Returns(VideoDirectoryPath + "/My.Video.mkv"); + video.Setup(m => m.ContainingFolderPath) + .Returns(containingFolderPath); + video.Setup(m => m.GetInternalMetadataPath()) + .Returns(metadataPath); + + string pathNotFoundRegex = metadataDirectory ? MetadataDirectoryRegex : VideoDirectoryRegex; + + var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict); + // any path other than test target exists and provides an empty listing + directoryService.Setup(ds => ds.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + + _subtitleResolver.GetExternalFiles(video.Object, directoryService.Object, false); + + directoryService.Verify( + ds => ds.GetFilePaths(It.IsRegex(pathNotFoundRegex), It.IsAny<bool>(), It.IsAny<bool>()), + Times.Never); + } + + [Theory] + [InlineData("My.Video.mkv", "My.Video.srt", null)] + [InlineData("My.Video.mkv", "My.Video.en.srt", "eng")] + [InlineData("My.Video.mkv", "My.Video.en.srt", "eng", true)] + [InlineData("Example Movie (2021).mp4", "Example Movie (2021).English.Srt", "eng")] + [InlineData("[LTDB] Who Framed Roger Rabbit (1998) - [Bluray-1080p].mkv", "[LTDB] Who Framed Roger Rabbit (1998) - [Bluray-1080p].en.srt", "eng")] + public void GetExternalFiles_NameMatching_MatchesAndParsesToken(string movie, string file, string? language, bool metadataDirectory = false) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = VideoDirectoryPath + "/" + movie + }; + + var directoryService = GetDirectoryServiceForExternalFile(file, metadataDirectory); + var streams = _subtitleResolver.GetExternalFiles(video, directoryService, false).ToList(); + + Assert.Single(streams); + var actual = streams[0]; + Assert.Equal(language, actual.Language); + Assert.Null(actual.Title); + } + + [Theory] + [InlineData("cover.jpg")] + [InlineData("My.Video.mp3")] + [InlineData("My.Video.png")] + [InlineData("My.Video.txt")] + [InlineData("My.Video Sequel.srt")] + [InlineData("Some.Other.Video.srt")] + public void GetExternalFiles_NameMatching_RejectsNonMatches(string file) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = VideoDirectoryPath + "/My.Video.mkv" + }; + + var directoryService = GetDirectoryServiceForExternalFile(file); + var streams = _subtitleResolver.GetExternalFiles(video, directoryService, false).ToList(); + + Assert.Empty(streams); + } + + [Theory] + [InlineData("https://url.com/My.Video.mkv")] + [InlineData(VideoDirectoryPath)] // valid but no files found for this test + public async void GetExternalStreams_BadPaths_ReturnsNoSubtitles(string path) + { + // need a media source manager capable of returning something other than file protocol + var mediaSourceManager = new Mock<IMediaSourceManager>(); + mediaSourceManager.Setup(m => m.GetPathProtocol(It.IsRegex(@"http.*"))) + .Returns(MediaProtocol.Http); + BaseItem.MediaSourceManager = mediaSourceManager.Object; + + var video = new Movie + { + Path = path + }; + + var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict); + directoryService.Setup(ds => ds.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + + var mediaEncoder = Mock.Of<IMediaEncoder>(MockBehavior.Strict); + var fileSystem = Mock.Of<IFileSystem>(); + + var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder, fileSystem, new NamingOptions()); + + var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService.Object, false, CancellationToken.None); + + Assert.Empty(streams); + } + + private static TheoryData<string, MediaStream[], MediaStream[]> GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data() + { + var data = new TheoryData<string, MediaStream[], MediaStream[]>(); + + // filename and stream have no metadata set + string file = "My.Video.srt"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0) + }); + + // filename has metadata + file = "My.Video.Title1.default.forced.en.srt"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title1", 0, true, true) + }); + + // single stream with metadata + file = "My.Video.mks"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title", 0, true, true) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title", 0, true, true) + }); + + // stream wins for title/language, filename wins for flags when conflicting + file = "My.Video.Title2.default.forced.en.srt"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 0) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 0, true, true) + }); + + // multiple stream with metadata - filename flags ignored but other data filled in when missing from stream + file = "My.Video.Title3.default.forced.en.srt"; + data.Add( + file, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0, true, true), + CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 1) + }, + new[] + { + CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title3", 0, true, true), + CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 1) + }); + + return data; + } + + [Theory] + [MemberData(nameof(GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data))] + public async void GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly(string file, MediaStream[] inputStreams, MediaStream[] expectedStreams) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = VideoDirectoryPath + "/My.Video.mkv" + }; + + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo + { + MediaStreams = inputStreams.ToList() + })); + + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex))) + .Returns(true); + + var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + + var directoryService = GetDirectoryServiceForExternalFile(file); + var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None); + + Assert.Equal(expectedStreams.Length, streams.Count); + for (var i = 0; i < expectedStreams.Length; i++) + { + var expected = expectedStreams[i]; + var actual = streams[i]; + + Assert.True(actual.IsExternal); + Assert.Equal(expected.Index, actual.Index); + Assert.Equal(expected.Type, actual.Type); + Assert.Equal(expected.Path, actual.Path); + Assert.Equal(expected.IsDefault, actual.IsDefault); + Assert.Equal(expected.IsForced, actual.IsForced); + Assert.Equal(expected.Language, actual.Language); + Assert.Equal(expected.Title, actual.Title); + } + } + + [Theory] + [InlineData(1, 1)] + [InlineData(1, 2)] + [InlineData(2, 1)] + [InlineData(2, 2)] + public async void GetExternalStreams_StreamIndex_HandlesFilesAndContainers(int fileCount, int streamCount) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); + + var video = new Movie + { + Path = VideoDirectoryPath + "/My.Video.mkv" + }; + + var files = new string[fileCount]; + for (int i = 0; i < fileCount; i++) + { + files[i] = $"{VideoDirectoryPath}/My.Video.{i}.srt"; + } + + var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict); + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(files); + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + + List<MediaStream> GenerateMediaStreams() + { + var mediaStreams = new List<MediaStream>(); + for (int i = 0; i < streamCount; i++) + { + mediaStreams.Add(new()); + } + + return mediaStreams; + } + + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo + { + MediaStreams = GenerateMediaStreams() + })); + + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex))) + .Returns(true); + + var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + + int startIndex = 1; + var streams = await subtitleResolver.GetExternalStreamsAsync(video, startIndex, directoryService.Object, false, CancellationToken.None); + + Assert.Equal(fileCount * streamCount, streams.Count); + for (var i = 0; i < streams.Count; i++) + { + Assert.Equal(startIndex + i, streams[i].Index); + // intentional integer division to ensure correct number of streams come back from each file + Assert.Matches(@$".*\.{i / streamCount}\.srt", streams[i].Path); + } + } + + private static MediaStream CreateMediaStream(string path, string? language, string? title, int index, bool isForced = false, bool isDefault = false) + { + return new MediaStream + { + Index = index, + Type = MediaStreamType.Subtitle, + Path = path, + IsDefault = isDefault, + IsForced = isForced, + Language = language, + Title = title + }; + } + + /// <summary> + /// Provides an <see cref="IDirectoryService"/> that when queried for the test video/metadata directory will return a path including the provided file name. + /// </summary> + /// <param name="file">The name of the file to locate.</param> + /// <param name="useMetadataDirectory"><c>true</c> if the file belongs in the metadata directory.</param> + /// <returns>A mocked <see cref="IDirectoryService"/>.</returns> + public static IDirectoryService GetDirectoryServiceForExternalFile(string file, bool useMetadataDirectory = false) + { + var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict); + if (useMetadataDirectory) + { + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(new[] { MetadataDirectoryPath + "/" + file }); + } + else + { + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(new[] { VideoDirectoryPath + "/" + file }); + directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>())) + .Returns(Array.Empty<string>()); + } + + return directoryService.Object; + } +} diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs index 040ea5d1d..0e6457ce3 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs @@ -1,129 +1,86 @@ -#pragma warning disable CA1002 // Do not expose generic lists - using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Emby.Naming.Common; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; using MediaBrowser.Providers.MediaInfo; using Moq; using Xunit; -namespace Jellyfin.Providers.Tests.MediaInfo +namespace Jellyfin.Providers.Tests.MediaInfo; + +public class SubtitleResolverTests { - public class SubtitleResolverTests - { - public static TheoryData<List<MediaStream>, string, int, string[], MediaStream[]> AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData() - { - var data = new TheoryData<List<MediaStream>, string, int, string[], MediaStream[]>(); + private readonly SubtitleResolver _subtitleResolver; - var index = 0; - data.Add( - new List<MediaStream>(), - "/video/My.Video.mkv", - index, - new[] - { - "/video/My.Video.mp3", - "/video/My.Video.png", - "/video/My.Video.srt", - "/video/My.Video.txt", - "/video/My.Video.vtt", - "/video/My.Video.ass", - "/video/My.Video.sub", - "/video/My.Video.ssa", - "/video/My.Video.smi", - "/video/My.Video.sami", - "/video/My.Video.en.srt", - "/video/My.Video.default.en.srt", - "/video/My.Video.default.forced.en.srt", - "/video/My.Video.en.default.forced.srt", - "/video/My.Video.With.Additional.Garbage.en.srt", - "/video/My.Video With Additional Garbage.srt" - }, - new[] - { - CreateMediaStream("/video/My.Video.srt", "srt", null, index++), - CreateMediaStream("/video/My.Video.vtt", "vtt", null, index++), - CreateMediaStream("/video/My.Video.ass", "ass", null, index++), - CreateMediaStream("/video/My.Video.sub", "sub", null, index++), - CreateMediaStream("/video/My.Video.ssa", "ssa", null, index++), - CreateMediaStream("/video/My.Video.smi", "smi", null, index++), - CreateMediaStream("/video/My.Video.sami", "sami", null, index++), - CreateMediaStream("/video/My.Video.en.srt", "srt", "en", index++), - CreateMediaStream("/video/My.Video.default.en.srt", "srt", "en", index++, isDefault: true), - CreateMediaStream("/video/My.Video.default.forced.en.srt", "srt", "en", index++, isForced: true, isDefault: true), - CreateMediaStream("/video/My.Video.en.default.forced.srt", "srt", "en", index++, isForced: true, isDefault: true), - CreateMediaStream("/video/My.Video.With.Additional.Garbage.en.srt", "srt", "en", index), - }); + public SubtitleResolverTests() + { + // prep BaseItem and Video for calls made that expect managers + Video.LiveTvManager = Mock.Of<ILiveTvManager>(); - return data; - } + var applicationPaths = new Mock<IServerApplicationPaths>().Object; + var serverConfig = new Mock<IServerConfigurationManager>(); + serverConfig.Setup(c => c.ApplicationPaths) + .Returns(applicationPaths); + BaseItem.ConfigurationManager = serverConfig.Object; - [Theory] - [MemberData(nameof(AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData))] - public void AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles(List<MediaStream> streams, string videoPath, int startIndex, string[] files, MediaStream[] expectedResult) - { - new SubtitleResolver(Mock.Of<ILocalizationManager>()).AddExternalSubtitleStreams(streams, videoPath, startIndex, files); + // build resolver to test with + var localizationManager = Mock.Of<ILocalizationManager>(); - Assert.Equal(expectedResult.Length, streams.Count); - for (var i = 0; i < expectedResult.Length; i++) + var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict); + mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>())) + .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo { - var expected = expectedResult[i]; - var actual = streams[i]; + MediaStreams = new List<MediaStream> + { + new() + } + })); - Assert.Equal(expected.Index, actual.Index); - Assert.Equal(expected.Type, actual.Type); - Assert.Equal(expected.IsExternal, actual.IsExternal); - Assert.Equal(expected.Path, actual.Path); - Assert.Equal(expected.IsDefault, actual.IsDefault); - Assert.Equal(expected.IsForced, actual.IsForced); - Assert.Equal(expected.Language, actual.Language); - } - } + var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.VideoDirectoryRegex))) + .Returns(true); + fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MediaInfoResolverTests.MetadataDirectoryRegex))) + .Returns(true); + + _subtitleResolver = new SubtitleResolver(localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions()); + } + + [Theory] + [InlineData("My.Video.srt", false, true)] + [InlineData("My.Video.mp3", false, false)] + [InlineData("My.Video.srt", true, true)] + [InlineData("My.Video.mp3", true, false)] + public async void GetExternalStreams_MixedFilenames_PicksSubtitles(string file, bool metadataDirectory, bool matches) + { + BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>(); - [Theory] - [InlineData("/video/My Video.mkv", "/video/My Video.srt", "srt", null, false, false)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.srt", "srt", null, false, false)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.foreign.srt", "srt", null, true, false)] - [InlineData("/video/My Video.mkv", "/video/My Video.forced.srt", "srt", null, true, false)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.default.srt", "srt", null, false, true)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.forced.default.srt", "srt", null, true, true)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.en.srt", "srt", "en", false, false)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.default.en.srt", "srt", "en", false, true)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.default.forced.en.srt", "srt", "en", true, true)] - [InlineData("/video/My.Video.mkv", "/video/My.Video.en.default.forced.srt", "srt", "en", true, true)] - public void AddExternalSubtitleStreams_GivenSingleFile_ReturnsExpectedSubtitle(string videoPath, string file, string codec, string? language, bool isForced, bool isDefault) + var video = new Movie { - var streams = new List<MediaStream>(); - var expected = CreateMediaStream(file, codec, language, 0, isForced, isDefault); + Path = MediaInfoResolverTests.VideoDirectoryPath + "/My.Video.mkv" + }; - new SubtitleResolver(Mock.Of<ILocalizationManager>()).AddExternalSubtitleStreams(streams, videoPath, 0, new[] { file }); + var directoryService = MediaInfoResolverTests.GetDirectoryServiceForExternalFile(file, metadataDirectory); + var streams = await _subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None); + if (matches) + { Assert.Single(streams); - var actual = streams[0]; - - Assert.Equal(expected.Index, actual.Index); - Assert.Equal(expected.Type, actual.Type); - Assert.Equal(expected.IsExternal, actual.IsExternal); - Assert.Equal(expected.Path, actual.Path); - Assert.Equal(expected.IsDefault, actual.IsDefault); - Assert.Equal(expected.IsForced, actual.IsForced); - Assert.Equal(expected.Language, actual.Language); + Assert.Equal(MediaStreamType.Subtitle, actual.Type); } - - private static MediaStream CreateMediaStream(string path, string codec, string? language, int index, bool isForced = false, bool isDefault = false) + else { - return new() - { - Index = index, - Codec = codec, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = path, - IsDefault = isDefault, - IsForced = isForced, - Language = language - }; + Assert.Empty(streams); } } } diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs index 1503a3392..7e88cdb20 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs @@ -116,9 +116,9 @@ namespace Jellyfin.Providers.Tests.MediaInfo } var mediaSourceManager = new Mock<IMediaSourceManager>(MockBehavior.Strict); - mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId == item.Id && q.Index == item.DefaultVideoStreamIndex))) + mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId.Equals(item.Id) && q.Index == item.DefaultVideoStreamIndex))) .Returns(defaultStreamList); - mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId == item.Id && q.Type == MediaStreamType.Video))) + mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId.Equals(item.Id) && q.Type == MediaStreamType.Video))) .Returns(mediaStreams); return mediaSourceManager.Object; } 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 3146f277f..2d0a7c031 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -21,8 +21,8 @@ <ItemGroup> <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" /> @@ -36,7 +36,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs index 5c7c983c2..c21871297 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs @@ -1,4 +1,4 @@ -using Emby.Naming.Common; +using Emby.Naming.Common; using Emby.Server.Implementations.Library.Resolvers.TV; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; @@ -7,6 +7,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -21,7 +22,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library { var parent = new Folder { Name = "extras" }; - var episodeResolver = new EpisodeResolver(_namingOptions); + var episodeResolver = new EpisodeResolver(Mock.Of<ILogger<EpisodeResolver>>(), _namingOptions); var itemResolveArgs = new ItemResolveArgs( Mock.Of<IServerApplicationPaths>(), Mock.Of<IDirectoryService>()) @@ -44,7 +45,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library // Have to create a mock because of moq proxies not being castable to a concrete implementation // https://github.com/jellyfin/jellyfin/blob/ab0cff8556403e123642dc9717ba778329554634/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs#L48 - var episodeResolver = new EpisodeResolverMock(_namingOptions); + var episodeResolver = new EpisodeResolverMock(Mock.Of<ILogger<EpisodeResolver>>(), _namingOptions); var itemResolveArgs = new ItemResolveArgs( Mock.Of<IServerApplicationPaths>(), Mock.Of<IDirectoryService>()) @@ -61,7 +62,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library private class EpisodeResolverMock : EpisodeResolver { - public EpisodeResolverMock(NamingOptions namingOptions) : base(namingOptions) + public EpisodeResolverMock(ILogger<EpisodeResolver> logger, NamingOptions namingOptions) : base(logger, namingOptions) { } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs index f5c8cc970..599599071 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs new file mode 100644 index 000000000..d59f2f4e5 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs @@ -0,0 +1,30 @@ +using System; +using Emby.Server.Implementations.Library; +using MediaBrowser.Model.Entities; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Library; + +public class MediaStreamSelectorTests +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GetDefaultAudioStreamIndex_EmptyStreams_Null(bool preferDefaultTrack) + { + Assert.Null(MediaStreamSelector.GetDefaultAudioStreamIndex(Array.Empty<MediaStream>(), Array.Empty<string>(), preferDefaultTrack)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GetDefaultAudioStreamIndex_WithoutDefault_NotNull(bool preferDefaultTrack) + { + var streams = new[] + { + new MediaStream() + }; + + Assert.NotNull(MediaStreamSelector.GetDefaultAudioStreamIndex(streams, Array.Empty<string>(), preferDefaultTrack)); + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs index f2efcddba..efc3ac0c2 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MovieResolverTests.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -17,7 +18,7 @@ public class MovieResolverTests [Fact] public void Resolve_GivenLocalAlternateVersion_ResolvesToVideo() { - var movieResolver = new MovieResolver(Mock.Of<IImageProcessor>(), _namingOptions); + var movieResolver = new MovieResolver(Mock.Of<IImageProcessor>(), Mock.Of<ILogger<MovieResolver>>(), _namingOptions); var itemResolveArgs = new ItemResolveArgs( Mock.Of<IServerApplicationPaths>(), Mock.Of<IDirectoryService>()) diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs index 3396a94e5..0afb6f88d 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs @@ -14,6 +14,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers { private readonly JellyfinApplicationFactory _factory; private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.Options; + private static string? _accessToken; public DashboardControllerTests(JellyfinApplicationFactory factory) { @@ -57,6 +58,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers public async Task GetConfigurationPages_NoParams_AllConfigurationPages() { var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false); @@ -71,6 +73,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers public async Task GetConfigurationPages_True_MainMenuConfigurationPages() { var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false); diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs index 588e25a82..9d34c39a2 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs @@ -130,7 +130,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var users = await JsonSerializer.DeserializeAsync<UserDto[]>( await client.GetStreamAsync("Users").ConfigureAwait(false), _jsonOpions).ConfigureAwait(false); - var user = users!.First(x => x.Id == _testUserId); + var user = users!.First(x => x.Id.Equals(_testUserId)); Assert.True(user.HasPassword); Assert.True(user.HasConfiguredPassword); } @@ -153,7 +153,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var users = await JsonSerializer.DeserializeAsync<UserDto[]>( await client.GetStreamAsync("Users").ConfigureAwait(false), _jsonOpions).ConfigureAwait(false); - var user = users!.First(x => x.Id == _testUserId); + var user = users!.First(x => x.Id.Equals(_testUserId)); Assert.False(user.HasPassword); Assert.False(user.HasConfiguredPassword); } diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 6cc879896..8e0a208af 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,14 +9,14 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="Xunit.Priority" Version="1.1.6" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.2" /> </ItemGroup> <ItemGroup> @@ -33,7 +33,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 3d34a18e7..adaf624a9 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -7,11 +7,11 @@ using MediaBrowser.Common; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Jellyfin.Server.Integration.Tests { @@ -74,7 +74,7 @@ namespace Jellyfin.Server.Integration.Tests appPaths, loggerFactory, commandLineOpts, - new ConfigurationBuilder().Build()); + startupConfig); _disposableComponents.Add(appHost); var serviceCollection = new ServiceCollection(); appHost.Init(serviceCollection); diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 64cf3a6bb..90bd74bd9 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,13 +10,13 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Moq" Version="4.17.2" /> </ItemGroup> <!-- Code Analyzers --> @@ -26,7 +26,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index 1bb2115cc..d8bc4d596 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -13,8 +13,8 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> - <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="coverlet.collector" Version="3.1.2" /> @@ -27,7 +27,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> |
