aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj2
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj2
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj2
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj2
-rw-r--r--tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj2
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonFlagEnumTests.cs28
-rw-r--r--tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj2
-rw-r--r--tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj2
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs466
-rw-r--r--tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs68
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj9
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json332
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json430
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json448
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-DirectMedia.json90
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json441
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json137
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json137
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Null.json9
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json211
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json211
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json357
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json139
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json189
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json189
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json73
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json72
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json73
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json72
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json73
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json72
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json72
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json72
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json87
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json87
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json89
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json71
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json75
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json89
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json91
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json74
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-raw.json102
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj2
-rw-r--r--tests/Jellyfin.Networking.Tests/Configuration/NetworkConfigurationTests.cs28
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj2
-rw-r--r--tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj2
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs2
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs4
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs3
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs4
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj2
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj2
55 files changed, 5264 insertions, 42 deletions
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 6f3b83455..0b9d8f7c0 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -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 1ad0f4e00..ee51562b4 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -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 9e6f7c0c1..d2087b023 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -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 4918e2e82..c8c526f4d 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -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 55125eb11..bd8667a84 100644
--- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
+++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
@@ -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 c53dab6d8..cc36dc4fa 100644
--- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
@@ -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 268631e58..de1fcab59 100644
--- a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
@@ -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 1a7c21084..e186a488a 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -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.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 9da80c312..5fdb22546 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -8,12 +8,19 @@
<ItemGroup>
<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/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index ea86906e7..69ba5d86a 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -30,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.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 e15f59e5a..ba39cc0ff 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -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 9d6923d05..6d52a3cd6 100644
--- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
+++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
@@ -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/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/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 55920c928..2d0a7c031 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -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.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 55101ce10d..8e0a208af 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -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.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index f5d60d2d3..90bd74bd9 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -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 7689d1da3..d8bc4d596 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -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>