From a3057afde82f314d01e5af72acb75beb4e08f778 Mon Sep 17 00:00:00 2001 From: Isaac Gordezky Date: Mon, 10 Jan 2022 02:53:15 +0000 Subject: [PATCH 01/10] StreamBuilder basic tests --- .../Jellyfin.Dlna.Tests.csproj | 6 + .../Jellyfin.Dlna.Tests/StreamBuilderTests.cs | 235 +++++++++ .../Test Data/DeviceProfile-AndroidPixel.json | 332 +++++++++++++ .../Test Data/DeviceProfile-Chrome-NoHLS.json | 430 +++++++++++++++++ .../Test Data/DeviceProfile-Chrome.json | 448 ++++++++++++++++++ .../Test Data/DeviceProfile-DirectMedia.json | 90 ++++ .../Test Data/DeviceProfile-Firefox.json | 441 +++++++++++++++++ .../DeviceProfile-JellyfinMediaPlayer.json | 137 ++++++ .../Test Data/DeviceProfile-LowBandwidth.json | 137 ++++++ .../Test Data/DeviceProfile-Null.json | 9 + .../Test Data/DeviceProfile-RokuSSPlus.json | 211 +++++++++ .../DeviceProfile-RokuSSPlusNext.json | 211 +++++++++ .../Test Data/DeviceProfile-SafariNext.json | 371 +++++++++++++++ .../DeviceProfile-TranscodeMedia.json | 123 +++++ .../Test Data/DeviceProfile-Yatse.json | 189 ++++++++ .../Test Data/DeviceProfile-Yatse2.json | 189 ++++++++ ...MediaSourceInfo-mkv-vp9-aac-srt-2600k.json | 73 +++ ...MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json | 72 +++ ...iaSourceInfo-mkv-vp9-vorbis-srt-2600k.json | 73 +++ ...iaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json | 72 +++ ...ediaSourceInfo-mp4-h264-aac-srt-2600k.json | 72 +++ ...ediaSourceInfo-mp4-h264-aac-vtt-2600k.json | 72 +++ ...SourceInfo-mp4-h264-ac3-aac-srt-2600k.json | 87 ++++ ...rceInfo-mp4-h264-ac3-aacDef-srt-2600k.json | 87 ++++ ...rceInfo-mp4-h264-ac3-aacExt-srt-2600k.json | 89 ++++ ...ediaSourceInfo-mp4-h264-ac3-srt-2600k.json | 71 +++ ...diaSourceInfo-mp4-hevc-aac-srt-15200k.json | 75 +++ ...ourceInfo-mp4-hevc-ac3-aac-srt-15200k.json | 89 ++++ ...ceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json | 91 ++++ ...diaSourceInfo-mp4-hevc-ac3-srt-15200k.json | 74 +++ .../Test Data/MediaSourceInfo-raw.json | 102 ++++ 31 files changed, 4758 insertions(+) create mode 100644 tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-AndroidPixel.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-DirectMedia.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Firefox.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-LowBandwidth.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Null.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlus.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse2.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-raw.json diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index cb15362742..a97c78ab37 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -14,6 +14,12 @@ + + + PreserveNewest + + + diff --git a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs new file mode 100644 index 0000000000..abb853b2ab --- /dev/null +++ b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs @@ -0,0 +1,235 @@ +using System; +using System.IO; +using System.Linq; +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.MediaBrowser.Model.Tests +{ + public class StreamBuilderTests + { + [Theory] + // Chrome + [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream + [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] + [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] + [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false' + [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false' + [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + // Firefox + [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream + [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] + [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] + [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false' + [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false' + [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + // Safari + [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay + [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay + // AndroidPixel + [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] + [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] + // Yatse + [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, true)] + [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + // RokuSSPlus + [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream + [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectStream + // JellyfinMediaPlayer + [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + // TranscodeMedia + [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + // DirectMedia + [InlineData("DirectMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("DirectMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("DirectMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("DirectMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("DirectMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("DirectMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("DirectMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + // LowBandwidth + [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + // Null + [InlineData("Null", "mp4-h264-aac-vtt-2600k", null)] // #6450 should be DirectPlay + [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null)] // #6450 should be DirectPlay + [InlineData("Null", "mp4-h264-ac3-srt-2600k", null)] // #6450 should be DirectPlay + [InlineData("Null", "mp4-hevc-aac-srt-15200k", null)] // #6450 should be DirectPlay + [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null)] // #6450 should be DirectPlay + [InlineData("Null", "mkv-vp9-aac-srt-2600k", null)] // #6450 should be DirectPlay + [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null)] // #6450 should be DirectPlay + [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null)] // #6450 should be DirectPlay + public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, bool fullTranscode = false) + { + var builder = GetStreamBuilder(); + var options = await GetVideoOptions(deviceName, mediaSource); + + var val = builder.BuildVideoItem(options); + Assert.NotNull(val); + + if (playMethod != null) + { + Assert.Equal(playMethod, val.PlayMethod); + } + + var videoStreams = options.MediaSources.SelectMany(source => source.MediaStreams).Where(stream => stream.Type == MediaStreamType.Video); + var audioStreams = options.MediaSources.SelectMany(source => source.MediaStreams).Where(stream => stream.Type == MediaStreamType.Audio); + + var url = new UriBuilder(val.ToUrl("https://server/", "ACCESSTOKEN")); + var query = System.Web.HttpUtility.ParseQueryString(url.Query); + + if (playMethod == PlayMethod.DirectPlay) + { + // Assert.Contains(query.Get("VidoeCodec"), videoStreams.Select(stream => stream.Codec)); + // Assert.Contains(query.Get("AudioCodec"), audioStreams.Select(stream => stream.Codec)); + Assert.Contains( + videoStreams, + stream => val.TargetVideoCodec.Contains(stream.Codec)); + Assert.Contains( + audioStreams, + stream => val.TargetAudioCodec.Contains(stream.Codec)); + } + + if (playMethod == PlayMethod.DirectStream) + { + Assert.Matches("stream[.][^.]+$", url.Path); + } + + if (playMethod == PlayMethod.Transcode) + { + if (fullTranscode) + { + Assert.Equal("hls", val.SubProtocol); + Assert.EndsWith("master.m3u8", url.Path, StringComparison.InvariantCulture); + + // Assert.All( + // videoStreams, + // stream => Assert.DoesNotContain(stream.Codec, val.TargetVideoCodec)); + } + else + { + Assert.Equal("hls", val.SubProtocol); + Assert.EndsWith("master.m3u8", url.Path, StringComparison.InvariantCulture); + + Assert.Contains( + videoStreams, + stream => val.TargetVideoCodec.Contains(stream.Codec)); + // Assert.All( + // audioStreams, + // stream => Assert.DoesNotContain(stream.Codec, val.TargetAudioCodec)); + + Assert.False(val.EstimateContentLength); + Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); + // Assert.True(val.CopyTimestamps); + + var videoStream = videoStreams.First(stream => val.TargetVideoCodec.Contains(stream.Codec)); + + Assert.Contains(videoStream.Codec, val.TargetVideoCodec); + // Assert.Contains(videoStream.Profile.ToLowerInvariant(), val.TargetVideoProfile.Split(",")); + // Assert.Equal(videoStream.Level, val.TargetVideoLevel); + // Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth); + // Assert.Equal(videoStream.BitRate, val.VideoBitrate); + } + } + + if (playMethod == null) + { + // what should the actual result be here? + Assert.Null(val.SubProtocol); + Assert.EndsWith("/stream", url.Path, StringComparison.InvariantCulture); + + Assert.False(val.EstimateContentLength); + Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); + // Assert.True(val.CopyTimestamps); + } + } + + private static async ValueTask TestData(string name) + { + var path = Path.Join("Test Data", typeof(T).Name + "-" + name + ".json"); + using (var stream = File.OpenRead(path)) + { + var value = await JsonSerializer.DeserializeAsync(stream, JsonDefaults.Options); + if (value != null) + { + return value; + } + + throw new Exception("Invalid test data: " + name); + } + } + + private StreamBuilder GetStreamBuilder() + { + var transcodeSupport = new Mock(); + var logger = new NullLogger(); + + return new StreamBuilder(transcodeSupport.Object, logger); + } + + private static async ValueTask GetVideoOptions(string deviceProfile, params string[] sources) + { + var mediaSources = sources.Select(src => TestData(src)) + .Select(val => val.Result) + .ToArray(); + var mediaSourceId = mediaSources[0]?.Id; + + var dp = await TestData(deviceProfile); + + return new VideoOptions() + { + ItemId = new Guid("11D229B7-2D48-4B95-9F9B-49F6AB75E613"), + MediaSourceId = mediaSourceId, + MediaSources = mediaSources, + DeviceId = "test-deviceId", + Profile = dp, + }; + } + } +} diff --git a/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-AndroidPixel.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-AndroidPixel.json new file mode 100644 index 0000000000..68ce3ea4ab --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json new file mode 100644 index 0000000000..5d1f5f1620 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-Chrome.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome.json new file mode 100644 index 0000000000..81bb97ac82 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-DirectMedia.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-DirectMedia.json new file mode 100644 index 0000000000..d1df7341e1 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-Firefox.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Firefox.json new file mode 100644 index 0000000000..9874793d37 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json new file mode 100644 index 0000000000..da9a1a4ada --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-LowBandwidth.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-LowBandwidth.json new file mode 100644 index 0000000000..82b73fb0f8 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-Null.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Null.json new file mode 100644 index 0000000000..d463bd896f --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlus.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlus.json new file mode 100644 index 0000000000..37b923558b --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json new file mode 100644 index 0000000000..542bf6370a --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json new file mode 100644 index 0000000000..8412d3e9bf --- /dev/null +++ b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json @@ -0,0 +1,371 @@ +{ + "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": "hevc,h264,vp8,vp9", + "Type": "Video", + "$type": "DirectPlayProfile" + }, + { + "Container": "mov", + "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis", + "VideoCodec": "hevc,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": "ts", + "Type": "Video", + "VideoCodec": "hevc,h264", + "AudioCodec": "aac,mp3,ac3,eac3", + "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,vpx", + "AudioCodec": "vorbis", + "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": "hevc,h264", + "AudioCodec": "aac,mp3,ac3,eac3,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": "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.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json new file mode 100644 index 0000000000..6fd1aaa24d --- /dev/null +++ b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json @@ -0,0 +1,123 @@ +{ + "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": "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.Dlna.Tests/Test Data/DeviceProfile-Yatse.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse.json new file mode 100644 index 0000000000..256c8dc2f0 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/DeviceProfile-Yatse2.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse2.json new file mode 100644 index 0000000000..256c8dc2f0 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json new file mode 100644 index 0000000000..0a85a13533 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json new file mode 100644 index 0000000000..2b932ff52a --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json new file mode 100644 index 0000000000..56b04b7898 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json new file mode 100644 index 0000000000..1ee7eade98 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json new file mode 100644 index 0000000000..21911843d1 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json new file mode 100644 index 0000000000..77954a31a2 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json new file mode 100644 index 0000000000..70bbb9d0d1 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json new file mode 100644 index 0000000000..036e41f077 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json new file mode 100644 index 0000000000..b81c4597f6 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json new file mode 100644 index 0000000000..b71fd4a6a2 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json new file mode 100644 index 0000000000..4c6409e7b0 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json new file mode 100644 index 0000000000..385bb72602 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json new file mode 100644 index 0000000000..fd1950bde1 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json new file mode 100644 index 0000000000..dde7c15ea0 --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-raw.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-raw.json new file mode 100644 index 0000000000..9ea55b805d --- /dev/null +++ b/tests/Jellyfin.Dlna.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 +} From d871dded9fc7b704f778764a73830ae6a481f3ff Mon Sep 17 00:00:00 2001 From: Isaac Gordezky Date: Sun, 23 Jan 2022 16:37:52 +0000 Subject: [PATCH 02/10] Convert TranscodeReason to Flags --- .../Controllers/UniversalAudioController.cs | 5 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../MediaEncoding/EncodingJobInfo.cs | 23 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 120 +++--- MediaBrowser.Model/Dlna/StreamInfo.cs | 5 +- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 2 +- MediaBrowser.Model/Session/TranscodeReason.cs | 67 ++-- .../Session/TranscodeReasonExtensions.cs | 22 ++ MediaBrowser.Model/Session/TranscodingInfo.cs | 12 +- .../Jellyfin.Dlna.Tests/StreamBuilderTests.cs | 363 +++++++++++++----- 10 files changed, 412 insertions(+), 209 deletions(-) create mode 100644 MediaBrowser.Model/Session/TranscodeReasonExtensions.cs diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index bc9527a0bc..962f637d4e 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -223,7 +224,7 @@ namespace Jellyfin.Api.Controllers DeInterlace = false, RequireNonAnamorphic = false, EnableMpegtsM2TsMode = false, - TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()), + TranscodeReasons = mediaSource.TranscodeReasons == MediaBrowser.Model.Session.TranscodeReason.None ? null : mediaSource.TranscodeReasons.Serialize(), Context = EncodingContext.Static, StreamOptions = new Dictionary(), EnableAdaptiveBitrateStreaming = true @@ -254,7 +255,7 @@ namespace Jellyfin.Api.Controllers CopyTimestamps = true, StartTimeTicks = startTimeTicks, SubtitleMethod = SubtitleDeliveryMethod.Embed, - TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()), + TranscodeReasons = mediaSource.TranscodeReasons == MediaBrowser.Model.Session.TranscodeReason.None ? null : mediaSource.TranscodeReasons.Serialize(), Context = EncodingContext.Static }; diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index c8762b7c54..49a3948688 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -479,7 +479,7 @@ namespace Jellyfin.Api.Helpers IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec), IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec), HardwareAccelerationType = hardwareAccelerationType, - TranscodeReasons = state.TranscodeReasons + TranscodeReason = state.TranscodeReason }); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index c4affa5678..0f5fdcc3c6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text.Json.Serialization; using Jellyfin.Data.Entities; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; @@ -23,7 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding public int? OutputAudioBitrate; public int? OutputAudioChannels; - private TranscodeReason[] _transcodeReasons = null; + private TranscodeReason? _transcodeReasons = null; public EncodingJobInfo(TranscodingJobType jobType) { @@ -34,25 +35,27 @@ namespace MediaBrowser.Controller.MediaEncoding SupportedSubtitleCodecs = Array.Empty(); } - public TranscodeReason[] TranscodeReasons + public TranscodeReason[] TranscodeReasons { get => TranscodeReason.ToArray(); } + + [JsonIgnore] + public TranscodeReason TranscodeReason { get { - if (_transcodeReasons == null) + if (!_transcodeReasons.HasValue) { if (BaseRequest.TranscodeReasons == null) { - return Array.Empty(); + _transcodeReasons = TranscodeReason.None; + return TranscodeReason.None; } - _transcodeReasons = BaseRequest.TranscodeReasons - .Split(',') - .Where(i => !string.IsNullOrEmpty(i)) - .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true)) - .ToArray(); + TranscodeReason reason = TranscodeReason.None; + Enum.TryParse(BaseRequest.TranscodeReasons, out reason); + _transcodeReasons = reason; } - return _transcodeReasons; + return _transcodeReasons.Value; } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index d2ca211505..7654337e70 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -143,7 +143,7 @@ namespace MediaBrowser.Model.Dlna }).ThenBy(streams.IndexOf); } - private static TranscodeReason? GetTranscodeReasonForFailedCondition(ProfileCondition condition) + private static TranscodeReason GetTranscodeReasonForFailedCondition(ProfileCondition condition) { switch (condition.Property) { @@ -161,7 +161,7 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.Has64BitOffsets: // TODO - return null; + return TranscodeReason.None; case ProfileConditionValue.Height: return TranscodeReason.VideoResolutionNotSupported; @@ -171,7 +171,7 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.IsAvc: // TODO - return null; + return TranscodeReason.None; case ProfileConditionValue.IsInterlaced: return TranscodeReason.InterlacedVideoNotSupported; @@ -181,15 +181,15 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.NumAudioStreams: // TODO - return null; + return TranscodeReason.None; case ProfileConditionValue.NumVideoStreams: // TODO - return null; + return TranscodeReason.None; case ProfileConditionValue.PacketLength: // TODO - return null; + return TranscodeReason.None; case ProfileConditionValue.RefFrames: return TranscodeReason.RefFramesNotSupported; @@ -217,13 +217,13 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.VideoTimestamp: // TODO - return null; + return TranscodeReason.None; case ProfileConditionValue.Width: return TranscodeReason.VideoResolutionNotSupported; default: - return null; + return TranscodeReason.None; } } @@ -290,7 +290,7 @@ namespace MediaBrowser.Model.Dlna var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options); var directPlayMethods = directPlayInfo.PlayMethods; - var transcodeReasons = directPlayInfo.TranscodeReasons.ToList(); + var transcodeReasons = directPlayInfo.TranscodeReasons; int? inputAudioChannels = audioStream?.Channels; int? inputAudioBitrate = audioStream?.BitDepth; @@ -331,11 +331,7 @@ namespace MediaBrowser.Model.Dlna if (!ConditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth)) { LogConditionFailure(options.Profile, "AudioCodecProfile", c, item); - var transcodeReason = GetTranscodeReasonForFailedCondition(c); - if (transcodeReason.HasValue) - { - transcodeReasons.Add(transcodeReason.Value); - } + transcodeReasons |= GetTranscodeReasonForFailedCondition(c); all = false; break; @@ -434,7 +430,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); } - playlistItem.TranscodeReasons = transcodeReasons.ToArray(); + playlistItem.TranscodeReasons = transcodeReasons; return playlistItem; } @@ -448,7 +444,7 @@ namespace MediaBrowser.Model.Dlna return options.GetMaxBitrate(isAudio); } - private (IEnumerable PlayMethods, IEnumerable TranscodeReasons) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options) + private (IEnumerable PlayMethods, TranscodeReason TranscodeReasons) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options) { DirectPlayProfile directPlayProfile = options.Profile.DirectPlayProfiles .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream)); @@ -465,12 +461,12 @@ namespace MediaBrowser.Model.Dlna } var playMethods = new List(); - var transcodeReasons = new List(); + var transcodeReasons = TranscodeReason.None; // While options takes the network and other factors into account. Only applies to direct stream if (item.SupportsDirectStream) { - if (IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) + if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) { if (options.EnableDirectStream) { @@ -479,7 +475,7 @@ namespace MediaBrowser.Model.Dlna } else { - transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit); + transcodeReasons |= TranscodeReason.ContainerBitrateExceedsLimit; } } @@ -487,7 +483,7 @@ namespace MediaBrowser.Model.Dlna // If device requirements are satisfied then allow both direct stream and direct play if (item.SupportsDirectPlay) { - if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay)) + if (IsItemBitrateEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay)) { if (options.EnableDirectPlay) { @@ -496,29 +492,26 @@ namespace MediaBrowser.Model.Dlna } else { - transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit); + transcodeReasons |= TranscodeReason.ContainerBitrateExceedsLimit; } } if (playMethods.Count > 0) { - transcodeReasons.Clear(); - } - else - { - transcodeReasons = transcodeReasons.Distinct().ToList(); + transcodeReasons = TranscodeReason.None; } return (playMethods, transcodeReasons); } - private static List GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable directPlayProfiles) + private static TranscodeReason GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable directPlayProfiles) { var mediaType = videoStream == null ? DlnaProfileType.Audio : DlnaProfileType.Video; var containerSupported = false; var audioSupported = false; var videoSupported = false; + var reasons = TranscodeReason.None; foreach (var profile in directPlayProfiles) { @@ -541,20 +534,20 @@ namespace MediaBrowser.Model.Dlna var list = new List(); if (!containerSupported) { - list.Add(TranscodeReason.ContainerNotSupported); + reasons |= TranscodeReason.ContainerNotSupported; } if (videoStream != null && !videoSupported) { - list.Add(TranscodeReason.VideoCodecNotSupported); + reasons |= TranscodeReason.VideoCodecNotSupported; } if (audioStream != null && !audioSupported) { - list.Add(TranscodeReason.AudioCodecNotSupported); + reasons |= TranscodeReason.AudioCodecNotSupported; } - return list; + return reasons; } private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles) @@ -679,8 +672,9 @@ namespace MediaBrowser.Model.Dlna // TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectPlay); var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectStream); - bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.DirectPlay); - bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.DirectPlay); + bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == TranscodeReason.None); + bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == TranscodeReason.None); + var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult; _logger.LogDebug( "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", @@ -689,8 +683,6 @@ namespace MediaBrowser.Model.Dlna isEligibleForDirectPlay, isEligibleForDirectStream); - var transcodeReasons = new List(); - if (isEligibleForDirectPlay || isEligibleForDirectStream) { // See if it can be direct played @@ -713,17 +705,13 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } - transcodeReasons.AddRange(directPlayInfo.TranscodeReasons); - } - - if (directPlayEligibilityResult.Reason.HasValue) - { - transcodeReasons.Add(directPlayEligibilityResult.Reason.Value); + transcodeReasons |= directPlayInfo.TranscodeReasons; } - if (directStreamEligibilityResult.Reason.HasValue) + if (playlistItem.PlayMethod != PlayMethod.Transcode) { - transcodeReasons.Add(directStreamEligibilityResult.Reason.Value); + playlistItem.TranscodeReasons = transcodeReasons; + return playlistItem; } // Can't direct play, find the transcoding profile @@ -869,7 +857,7 @@ namespace MediaBrowser.Model.Dlna } } - playlistItem.TranscodeReasons = transcodeReasons.ToArray(); + playlistItem.TranscodeReasons = transcodeReasons; return playlistItem; } @@ -1000,7 +988,7 @@ namespace MediaBrowser.Model.Dlna return 7168000; } - private (PlayMethod? PlayMethod, List TranscodeReasons) GetVideoDirectPlayProfile( + private (PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile( VideoOptions options, MediaSourceInfo mediaSource, MediaStream videoStream, @@ -1009,12 +997,12 @@ namespace MediaBrowser.Model.Dlna { if (options.ForceDirectPlay) { - return (PlayMethod.DirectPlay, new List()); + return (PlayMethod.DirectPlay, TranscodeReason.None); } if (options.ForceDirectStream) { - return (PlayMethod.DirectStream, new List()); + return (PlayMethod.DirectStream, TranscodeReason.None); } DeviceProfile profile = options.Profile; @@ -1089,11 +1077,7 @@ namespace MediaBrowser.Model.Dlna { LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource); - var transcodeReason = GetTranscodeReasonForFailedCondition(i); - var transcodeReasons = transcodeReason.HasValue - ? new List { transcodeReason.Value } - : new List(); - + var transcodeReasons = GetTranscodeReasonForFailedCondition(i); return (null, transcodeReasons); } } @@ -1133,11 +1117,7 @@ namespace MediaBrowser.Model.Dlna LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource); var transcodeReason = GetTranscodeReasonForFailedCondition(i); - var transcodeReasons = transcodeReason.HasValue - ? new List { transcodeReason.Value } - : new List(); - - return (null, transcodeReasons); + return (null, transcodeReason); } } @@ -1178,11 +1158,7 @@ namespace MediaBrowser.Model.Dlna { LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource); - var transcodeReason = GetTranscodeReasonForFailedCondition(i); - var transcodeReasons = transcodeReason.HasValue - ? new List { transcodeReason.Value } - : new List(); - + var transcodeReasons = GetTranscodeReasonForFailedCondition(i); return (null, transcodeReasons); } } @@ -1190,10 +1166,10 @@ namespace MediaBrowser.Model.Dlna if (isEligibleForDirectStream && mediaSource.SupportsDirectStream) { - return (PlayMethod.DirectStream, new List()); + return (PlayMethod.DirectStream, TranscodeReason.None); } - return (null, new List { TranscodeReason.ContainerBitrateExceedsLimit }); + return (null, TranscodeReason.ContainerBitrateExceedsLimit); } private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource) @@ -1209,7 +1185,7 @@ namespace MediaBrowser.Model.Dlna mediaSource.Path ?? "Unknown path"); } - private (bool DirectPlay, TranscodeReason? Reason) IsEligibleForDirectPlay( + private TranscodeReason IsEligibleForDirectPlay( MediaSourceInfo item, long maxBitrate, MediaStream subtitleStream, @@ -1217,6 +1193,7 @@ namespace MediaBrowser.Model.Dlna VideoOptions options, PlayMethod playMethod) { + var reason = TranscodeReason.None; if (subtitleStream != null) { var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, playMethod, _transcoderSupport, item.Container, null); @@ -1226,22 +1203,23 @@ namespace MediaBrowser.Model.Dlna && subtitleProfile.Method != SubtitleDeliveryMethod.Embed) { _logger.LogDebug("Not eligible for {0} due to unsupported subtitles", playMethod); - return (false, TranscodeReason.SubtitleCodecNotSupported); + reason |= TranscodeReason.SubtitleCodecNotSupported; } } - bool result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod); + bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod); if (!result) { - return (false, TranscodeReason.ContainerBitrateExceedsLimit); + reason |= TranscodeReason.ContainerBitrateExceedsLimit; } + // TODO:6450 support external audio in DirectStream? if (audioStream?.IsExternal == true) { - return (false, TranscodeReason.AudioIsExternal); + reason |= TranscodeReason.AudioIsExternal; } - return (true, null); + return reason; } public static SubtitleProfile GetSubtitleProfile( @@ -1401,7 +1379,7 @@ namespace MediaBrowser.Model.Dlna return null; } - private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod) + private bool IsItemBitrateEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod) { // Don't restrict by bitrate if coming from an external domain if (item.IsRemote) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index a678c54e7f..3b86d5f42c 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -23,7 +23,6 @@ namespace MediaBrowser.Model.Dlna AudioCodecs = Array.Empty(); VideoCodecs = Array.Empty(); SubtitleCodecs = Array.Empty(); - TranscodeReasons = Array.Empty(); StreamOptions = new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -103,7 +102,7 @@ namespace MediaBrowser.Model.Dlna public string PlaySessionId { get; set; } - public TranscodeReason[] TranscodeReasons { get; set; } + public TranscodeReason TranscodeReasons { get; set; } public Dictionary StreamOptions { get; private set; } @@ -799,7 +798,7 @@ namespace MediaBrowser.Model.Dlna if (!item.IsDirectStream) { - list.Add(new NameValuePair("TranscodeReasons", string.Join(',', item.TranscodeReasons.Distinct()))); + list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.Serialize())); } return list; diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 049e14333f..2281e6ae5d 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -109,7 +109,7 @@ namespace MediaBrowser.Model.Dto public int? AnalyzeDurationMs { get; set; } [JsonIgnore] - public TranscodeReason[] TranscodeReasons { get; set; } + public TranscodeReason TranscodeReasons { get; set; } public int? DefaultAudioStreamIndex { get; set; } diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs index 3c95df66d5..c3570840f9 100644 --- a/MediaBrowser.Model/Session/TranscodeReason.cs +++ b/MediaBrowser.Model/Session/TranscodeReason.cs @@ -1,32 +1,51 @@ #pragma warning disable CS1591 +using System; + namespace MediaBrowser.Model.Session { + [Flags] public enum TranscodeReason { - ContainerNotSupported = 0, - VideoCodecNotSupported = 1, - AudioCodecNotSupported = 2, - ContainerBitrateExceedsLimit = 3, - AudioBitrateNotSupported = 4, - AudioChannelsNotSupported = 5, - VideoResolutionNotSupported = 6, - UnknownVideoStreamInfo = 7, - UnknownAudioStreamInfo = 8, - AudioProfileNotSupported = 9, - AudioSampleRateNotSupported = 10, - AnamorphicVideoNotSupported = 11, - InterlacedVideoNotSupported = 12, - SecondaryAudioNotSupported = 13, - RefFramesNotSupported = 14, - VideoBitDepthNotSupported = 15, - VideoBitrateNotSupported = 16, - VideoFramerateNotSupported = 17, - VideoLevelNotSupported = 18, - VideoProfileNotSupported = 19, - AudioBitDepthNotSupported = 20, - SubtitleCodecNotSupported = 21, - DirectPlayError = 22, - AudioIsExternal = 23 + None = 0, + + // Primary + ContainerNotSupported = 1 << 0, + VideoCodecNotSupported = 1 << 1, + AudioCodecNotSupported = 1 << 2, + SubtitleCodecNotSupported = 1 << 3, + AudioIsExternal = 1 << 4, + SecondaryAudioNotSupported = 1 << 5, + + // Video Constraints + VideoProfileNotSupported = 1 << 6, + VideoLevelNotSupported = 1 << 7, + VideoResolutionNotSupported = 1 << 8, + VideoBitDepthNotSupported = 1 << 9, + VideoFramerateNotSupported = 1 << 10, + RefFramesNotSupported = 1 << 11, + AnamorphicVideoNotSupported = 1 << 12, + InterlacedVideoNotSupported = 1 << 13, + + // Audio Constraints + AudioChannelsNotSupported = 1 << 14, + AudioProfileNotSupported = 1 << 15, + AudioSampleRateNotSupported = 1 << 16, + AudioBitDepthNotSupported = 1 << 20, + + // Bitrate Constraints + ContainerBitrateExceedsLimit = 1 << 17, + VideoBitrateNotSupported = 1 << 18, + AudioBitrateNotSupported = 1 << 19, + + // Errors + UnknownVideoStreamInfo = 1 << 20, + UnknownAudioStreamInfo = 1 << 21, + DirectPlayError = 1 << 22, + + // Aliases + ContainerReasons = ContainerNotSupported | ContainerBitrateExceedsLimit, + AudioReasons = AudioCodecNotSupported | AudioBitrateNotSupported | AudioChannelsNotSupported | AudioProfileNotSupported | AudioSampleRateNotSupported | SecondaryAudioNotSupported | AudioBitDepthNotSupported | AudioIsExternal, + VideoReasons = VideoCodecNotSupported | VideoResolutionNotSupported | AnamorphicVideoNotSupported | InterlacedVideoNotSupported | VideoBitDepthNotSupported | VideoBitrateNotSupported | VideoFramerateNotSupported | VideoLevelNotSupported | RefFramesNotSupported, } } diff --git a/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs b/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs new file mode 100644 index 0000000000..c7a5095f74 --- /dev/null +++ b/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs @@ -0,0 +1,22 @@ +#pragma warning disable CS1591 + +using System; +using System.Linq; + +namespace MediaBrowser.Model.Session +{ + public static class TranscodeReasonExtensions + { + private static TranscodeReason[] values = Enum.GetValues(); + + public static string Serialize(this MediaBrowser.Model.Session.TranscodeReason reasons, string sep = ",") + { + return string.Join(sep, reasons.ToArray()); + } + + public static TranscodeReason[] ToArray(this MediaBrowser.Model.Session.TranscodeReason reasons) + { + return values.Where(r => r != 0 && reasons.HasFlag(r)).ToArray(); + } + } +} diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index 68ab691f88..78e5baad7e 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/MediaBrowser.Model/Session/TranscodingInfo.cs @@ -1,17 +1,12 @@ #nullable disable #pragma warning disable CS1591 -using System; +using System.Text.Json.Serialization; namespace MediaBrowser.Model.Session { public class TranscodingInfo { - public TranscodingInfo() - { - TranscodeReasons = Array.Empty(); - } - public string AudioCodec { get; set; } public string VideoCodec { get; set; } @@ -36,6 +31,9 @@ namespace MediaBrowser.Model.Session public HardwareEncodingType? HardwareAccelerationType { get; set; } - public TranscodeReason[] TranscodeReasons { get; set; } + public TranscodeReason[] TranscodeReasons { get => TranscodeReason.ToArray(); } + + [JsonIgnore] + public TranscodeReason TranscodeReason { get; set; } } } diff --git a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs index abb853b2ab..ccd95f7502 100644 --- a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs +++ b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text.Json; @@ -19,65 +20,69 @@ namespace Jellyfin.MediaBrowser.Model.Tests [Theory] // Chrome [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream - [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] - [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] - [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false' - [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false' + [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay + [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream + [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] + [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' + [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay // Firefox [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream - [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] - [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] - [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false' - [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false' + [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay + [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream + [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] + [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' + [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay // Safari [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("SafariNext", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay // AndroidPixel [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] - [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] + [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.DirectStream)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, true)] + [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay // RokuSSPlus [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream + [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectStream + [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectStream // JellyfinMediaPlayer [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay // TranscodeMedia - [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay // DirectMedia [InlineData("DirectMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay @@ -88,27 +93,120 @@ namespace Jellyfin.MediaBrowser.Model.Tests [InlineData("DirectMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay [InlineData("DirectMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay // LowBandwidth - [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay + [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay // Null - [InlineData("Null", "mp4-h264-aac-vtt-2600k", null)] // #6450 should be DirectPlay - [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null)] // #6450 should be DirectPlay - [InlineData("Null", "mp4-h264-ac3-srt-2600k", null)] // #6450 should be DirectPlay - [InlineData("Null", "mp4-hevc-aac-srt-15200k", null)] // #6450 should be DirectPlay - [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null)] // #6450 should be DirectPlay - [InlineData("Null", "mkv-vp9-aac-srt-2600k", null)] // #6450 should be DirectPlay - [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null)] // #6450 should be DirectPlay - [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null)] // #6450 should be DirectPlay - public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, bool fullTranscode = false) + [InlineData("Null", "mp4-h264-aac-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Null", "mp4-h264-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Null", "mp4-hevc-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay + public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") { - var builder = GetStreamBuilder(); var options = await GetVideoOptions(deviceName, mediaSource); + BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); + } + + [Theory] + // Chrome + [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream + [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] + [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' + [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' + [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + // Firefox + [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream + [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] + [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] + [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' + [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' + [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + // Safari + [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay + [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay + // AndroidPixel + [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [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.DirectStream)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] + [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + // RokuSSPlus + [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream + [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectStream + // JellyfinMediaPlayer + [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod?playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") + { + var options = await GetVideoOptions(deviceName, mediaSource); + options.AudioStreamIndex = 1; + options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count() - 1; + BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); + } + + [Theory] + // Chrome + [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay + [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450 should have container & profile video reasons? + // Firefox + [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay + [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450 should have container & profile video reasons? + // Yatse + [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported, "Transcode")] // #6450 should be DirectPlay + // RokuSSPlus + [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod playMethod, TranscodeReason why = TranscodeReason.None, 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; + BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); + } + + private void BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol) + { + if (string.IsNullOrEmpty(transcodeProtocol)) + { + transcodeProtocol = playMethod == PlayMethod.DirectStream ? "http" : "hls"; + } + + var builder = GetStreamBuilder(); var val = builder.BuildVideoItem(options); Assert.NotNull(val); @@ -118,63 +216,130 @@ namespace Jellyfin.MediaBrowser.Model.Tests Assert.Equal(playMethod, val.PlayMethod); } - var videoStreams = options.MediaSources.SelectMany(source => source.MediaStreams).Where(stream => stream.Type == MediaStreamType.Video); - var audioStreams = options.MediaSources.SelectMany(source => source.MediaStreams).Where(stream => stream.Type == MediaStreamType.Audio); + Assert.Equal(why, val.TranscodeReasons); + + var audioStreamIndexInput = options.AudioStreamIndex; + var targetVideoStream = val.TargetVideoStream; + var targetAudioStream = val.TargetAudioStream; - var url = new UriBuilder(val.ToUrl("https://server/", "ACCESSTOKEN")); - var query = System.Web.HttpUtility.ParseQueryString(url.Query); + 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) { - // Assert.Contains(query.Get("VidoeCodec"), videoStreams.Select(stream => stream.Codec)); - // Assert.Contains(query.Get("AudioCodec"), audioStreams.Select(stream => stream.Codec)); - Assert.Contains( - videoStreams, - stream => val.TargetVideoCodec.Contains(stream.Codec)); - Assert.Contains( - audioStreams, - stream => val.TargetAudioCodec.Contains(stream.Codec)); - } + // check expected container + var containers = ContainerProfile.SplitValue(mediaSource.Container); + Assert.Contains(uri.Extension, containers); - if (playMethod == PlayMethod.DirectStream) - { - Assert.Matches("stream[.][^.]+$", url.Path); - } + // 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.AudioCodecs); - if (playMethod == PlayMethod.Transcode) + // TODO: validate transcoding options as well + } + else if (playMethod == PlayMethod.DirectStream || playMethod == PlayMethod.Transcode) { - if (fullTranscode) + 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 + { + Assert.Equal("ts", val.Container); + Assert.Equal("m3u8", uri.Extension); + Assert.Equal("master", uri.Filename); Assert.Equal("hls", val.SubProtocol); - Assert.EndsWith("master.m3u8", url.Path, StringComparison.InvariantCulture); + } + + // Full transcode + if (transcodeMode == "Transcode") + { + // TODO: what else to validate here + if ((val.TranscodeReasons & TranscodeReason.ContainerReasons) == TranscodeReason.None) + { + // Assert.All( + // videoStreams, + // stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs)); + } - // Assert.All( - // videoStreams, - // stream => Assert.DoesNotContain(stream.Codec, val.TargetVideoCodec)); + // todo: fill out tests here } + + // DirectStream and Remux else { - Assert.Equal("hls", val.SubProtocol); - Assert.EndsWith("master.m3u8", url.Path, StringComparison.InvariantCulture); + // check expected video codec (1) + Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); + Assert.Single(val.TargetVideoCodec); - Assert.Contains( - videoStreams, - stream => val.TargetVideoCodec.Contains(stream.Codec)); - // Assert.All( - // audioStreams, - // stream => Assert.DoesNotContain(stream.Codec, val.TargetAudioCodec)); + 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.True(val.CopyTimestamps); - - var videoStream = videoStreams.First(stream => val.TargetVideoCodec.Contains(stream.Codec)); - - Assert.Contains(videoStream.Codec, val.TargetVideoCodec); - // Assert.Contains(videoStream.Profile.ToLowerInvariant(), val.TargetVideoProfile.Split(",")); + // Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, val.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? new string[0]); // Assert.Equal(videoStream.Level, val.TargetVideoLevel); // Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth); - // Assert.Equal(videoStream.BitRate, val.VideoBitrate); + // Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue); + + // audio codec not supported + if ((why & TranscodeReason.AudioCodecNotSupported) != TranscodeReason.None) + { + // 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); + } + }); + } + } } } @@ -182,7 +347,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests { // what should the actual result be here? Assert.Null(val.SubProtocol); - Assert.EndsWith("/stream", url.Path, StringComparison.InvariantCulture); + Assert.EndsWith("/stream", uri.Path, StringComparison.InvariantCulture); Assert.False(val.EstimateContentLength); Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); @@ -231,5 +396,23 @@ namespace Jellyfin.MediaBrowser.Model.Tests Profile = dp, }; } + + 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 = System.IO.Path.GetFileNameWithoutExtension(path); + var extension = System.IO.Path.GetExtension(path); + if (extension.Length > 0) + { + extension = extension.Substring(1); + } + + return (path, query, filename, extension); + } } } From 5e779f20ee88de93b75d06b255c93d0196c1255b Mon Sep 17 00:00:00 2001 From: Isaac Gordezky Date: Sun, 23 Jan 2022 23:49:14 +0000 Subject: [PATCH 03/10] Series: issue-6450 Issue: https://github.com/jellyfin/jellyfin/issues/6450 Enable DirectPlay responses Rewrite DirectPlay and DirectStream resolution Prefer copy transcode video codec options Enhance condition processor Support DirectStream and Transcode with parity Rework audio stream selection and add tests for ExternalAudio Update MediaInfoHelper to only call StreamBuilder once --- .../Controllers/MediaInfoController.cs | 10 +- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 183 +--- .../MediaEncoding/EncodingHelper.cs | 16 +- .../MediaEncoding/EncodingJobInfo.cs | 5 +- MediaBrowser.Model/Dlna/AudioOptions.cs | 2 + MediaBrowser.Model/Dlna/StreamBuilder.cs | 946 ++++++++++-------- MediaBrowser.Model/Dlna/VideoOptions.cs | 2 + MediaBrowser.Model/Dto/MediaSourceInfo.cs | 2 +- MediaBrowser.Model/Properties/AssemblyInfo.cs | 1 + MediaBrowser.Model/Session/TranscodeReason.cs | 19 +- .../Session/TranscodeReasonExtensions.cs | 24 +- MediaBrowser.Model/Session/TranscodingInfo.cs | 2 +- .../Jellyfin.Dlna.Tests/StreamBuilderTests.cs | 353 ++++--- 13 files changed, 801 insertions(+), 764 deletions(-) diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index b422eb78c6..75df18204d 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -126,7 +126,7 @@ namespace Jellyfin.Api.Controllers var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var profile = playbackInfoDto?.DeviceProfile; - _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile); + _logger.LogDebug("GetPostedPlaybackInfo profile: {@Profile}", profile); if (profile == null) { @@ -225,14 +225,6 @@ namespace Jellyfin.Api.Controllers } } - if (info.MediaSources != null) - { - foreach (var mediaSource in info.MediaSources) - { - _mediaInfoHelper.NormalizeMediaSourceContainer(mediaSource, profile!, DlnaProfileType.Video); - } - } - return info; } diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 3b8dc7e316..1fb7c67078 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -191,7 +191,9 @@ namespace Jellyfin.Api.Helpers DeviceId = auth.DeviceId, ItemId = item.Id, Profile = profile, - MaxAudioChannels = maxAudioChannels + MaxAudioChannels = maxAudioChannels, + AllowAudioStreamCopy = allowAudioStreamCopy, + AllowVideoStreamCopy = allowVideoStreamCopy }; if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase)) @@ -208,7 +210,7 @@ namespace Jellyfin.Api.Helpers mediaSource.SupportsDirectPlay = false; } - if (!enableDirectStream) + if (!enableDirectStream || !allowVideoStreamCopy) { mediaSource.SupportsDirectStream = false; } @@ -235,168 +237,79 @@ namespace Jellyfin.Api.Helpers user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)); } - // Beginning of Playback Determination: Attempt DirectPlay first - if (mediaSource.SupportsDirectPlay) - { - if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) - { - mediaSource.SupportsDirectPlay = false; - } - else - { - var supportsDirectStream = mediaSource.SupportsDirectStream; - - // Dummy this up to fool StreamBuilder - mediaSource.SupportsDirectStream = true; - options.MaxBitrate = maxBitrate; + options.MaxBitrate = GetMaxBitrate(maxBitrate, user, ipAddress); - if (item is Audio) - { - if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) - { - options.ForceDirectPlay = true; - } - } - else if (item is Video) - { - if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) - && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) - && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) - { - options.ForceDirectPlay = true; - } - } + if (!options.ForceDirectStream) + { + // direct-stream http streaming is currently broken + options.EnableDirectStream = false; + } - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) - ? streamBuilder.BuildAudioItem(options) - : streamBuilder.BuildVideoItem(options); + // Beginning of Playback Determination + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) + ? streamBuilder.BuildAudioItem(options) + : streamBuilder.BuildVideoItem(options); - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectPlay = false; - } + if (streamInfo != null) + { + streamInfo.PlaySessionId = playSessionId; + streamInfo.StartPositionTicks = startTimeTicks; - // Set this back to what it was - mediaSource.SupportsDirectStream = supportsDirectStream; + mediaSource.SupportsDirectPlay = streamInfo.PlayMethod == PlayMethod.DirectPlay; + // Players do not handle this being set according to PlayMethod + mediaSource.SupportsDirectStream = options.EnableDirectStream ? streamInfo.PlayMethod == PlayMethod.DirectPlay || streamInfo.PlayMethod == PlayMethod.DirectStream : streamInfo.PlayMethod == PlayMethod.DirectPlay; + mediaSource.SupportsTranscoding = streamInfo.PlayMethod == PlayMethod.DirectStream || mediaSource.TranscodingContainer != null; - if (streamInfo != null) + if (item is Audio) + { + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; + mediaSource.SupportsTranscoding = false; } } - } - - if (mediaSource.SupportsDirectStream) - { - if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) + else if (item is Video) { - mediaSource.SupportsDirectStream = false; - } - else - { - options.MaxBitrate = GetMaxBitrate(maxBitrate, user, ipAddress); - - if (item is Audio) + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) { - if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) - { - options.ForceDirectStream = true; - } - } - else if (item is Video) - { - if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) - && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) - && user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) - { - options.ForceDirectStream = true; - } - } - - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) - ? streamBuilder.BuildAudioItem(options) - : streamBuilder.BuildVideoItem(options); - - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectStream = false; - } - - if (streamInfo != null) - { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; + mediaSource.SupportsTranscoding = false; } } - } - - if (mediaSource.SupportsTranscoding) - { - options.MaxBitrate = GetMaxBitrate(maxBitrate, user, ipAddress); - - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) - ? streamBuilder.BuildAudioItem(options) - : streamBuilder.BuildVideoItem(options); if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) { - if (streamInfo != null) - { - streamInfo.PlaySessionId = playSessionId; - streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); - mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; - mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; - mediaSource.TranscodingContainer = streamInfo.Container; - mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - - // Do this after the above so that StartPositionTicks is set - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; - } + mediaSource.SupportsDirectPlay = false; + mediaSource.SupportsDirectStream = false; + + mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); + mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; + mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; + mediaSource.TranscodingContainer = streamInfo.Container; + mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; } else { - if (streamInfo != null) + if (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream) { - streamInfo.PlaySessionId = playSessionId; + streamInfo.PlayMethod = PlayMethod.Transcode; + mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); - if (streamInfo.PlayMethod == PlayMethod.Transcode) + if (!allowVideoStreamCopy) { - streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); - - if (!allowVideoStreamCopy) - { - mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; - } - - if (!allowAudioStreamCopy) - { - mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; - } - - mediaSource.TranscodingContainer = streamInfo.Container; - mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; + mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; } if (!allowAudioStreamCopy) { mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; } - - mediaSource.TranscodingContainer = streamInfo.Container; - mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - - // Do this after the above so that StartPositionTicks is set - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; } } + + // Do this after the above so that StartPositionTicks is set + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; } foreach (var attachment in mediaSource.MediaAttachments) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index f7248acacd..b4aff60e17 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1798,7 +1798,7 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - return request.EnableAutoStreamCopy; + return true; } public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable supportedAudioCodecs) @@ -1855,17 +1855,11 @@ namespace MediaBrowser.Controller.MediaEncoding } // Video bitrate must fall within requested value - if (request.AudioBitRate.HasValue) + if (request.AudioBitRate.HasValue + && audioStream.BitDepth.HasValue + && audioStream.BitRate.Value > request.AudioBitRate.Value) { - if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value <= 0) - { - return false; - } - - if (audioStream.BitRate.Value > request.AudioBitRate.Value) - { - return false; - } + return false; } return request.EnableAutoStreamCopy; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 0f5fdcc3c6..23067c7b18 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.MediaEncoding SupportedSubtitleCodecs = Array.Empty(); } - public TranscodeReason[] TranscodeReasons { get => TranscodeReason.ToArray(); } + public TranscodeReason[] TranscodeReasons => TranscodeReason.ToArray(); [JsonIgnore] public TranscodeReason TranscodeReason @@ -50,8 +50,7 @@ namespace MediaBrowser.Controller.MediaEncoding return TranscodeReason.None; } - TranscodeReason reason = TranscodeReason.None; - Enum.TryParse(BaseRequest.TranscodeReasons, out reason); + _ = Enum.TryParse(BaseRequest.TranscodeReasons, out var reason); _transcodeReasons = reason; } diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs index 4d4d8d78cb..33755e7462 100644 --- a/MediaBrowser.Model/Dlna/AudioOptions.cs +++ b/MediaBrowser.Model/Dlna/AudioOptions.cs @@ -27,6 +27,8 @@ namespace MediaBrowser.Model.Dlna public bool ForceDirectStream { get; set; } + public bool AllowAudioStreamCopy { get; set; } + public Guid ItemId { get; set; } public MediaSourceInfo[] MediaSources { get; set; } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 7654337e70..0f48e985d5 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -15,6 +15,12 @@ namespace MediaBrowser.Model.Dlna { public class StreamBuilder { + // Aliases + internal const TranscodeReason ContainerReasons = TranscodeReason.ContainerNotSupported | TranscodeReason.ContainerBitrateExceedsLimit; + internal const TranscodeReason AudioReasons = TranscodeReason.AudioCodecNotSupported | TranscodeReason.AudioBitrateNotSupported | TranscodeReason.AudioChannelsNotSupported | TranscodeReason.AudioProfileNotSupported | TranscodeReason.AudioSampleRateNotSupported | TranscodeReason.SecondaryAudioNotSupported | TranscodeReason.AudioBitDepthNotSupported | TranscodeReason.AudioIsExternal; + internal const TranscodeReason VideoReasons = TranscodeReason.VideoCodecNotSupported | TranscodeReason.VideoResolutionNotSupported | TranscodeReason.AnamorphicVideoNotSupported | TranscodeReason.InterlacedVideoNotSupported | TranscodeReason.VideoBitDepthNotSupported | TranscodeReason.VideoBitrateNotSupported | TranscodeReason.VideoFramerateNotSupported | TranscodeReason.VideoLevelNotSupported | TranscodeReason.RefFramesNotSupported; + internal const TranscodeReason DirectStreamReasons = AudioReasons | TranscodeReason.ContainerNotSupported; + private readonly ILogger _logger; private readonly ITranscoderSupport _transcoderSupport; @@ -227,7 +233,7 @@ namespace MediaBrowser.Model.Dlna } } - public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type) + public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type, DirectPlayProfile playProfile = null) { if (string.IsNullOrEmpty(inputContainer)) { @@ -236,16 +242,12 @@ namespace MediaBrowser.Model.Dlna var formats = ContainerProfile.SplitValue(inputContainer); - if (formats.Length == 1) - { - return formats[0]; - } - if (profile != null) { + var playProfiles = playProfile == null ? profile.DirectPlayProfiles : new[] { playProfile }; foreach (var format in formats) { - foreach (var directPlayProfile in profile.DirectPlayProfiles) + foreach (var directPlayProfile in playProfiles) { if (directPlayProfile.Type == type && directPlayProfile.SupportsContainer(format)) @@ -287,9 +289,9 @@ namespace MediaBrowser.Model.Dlna var audioStream = item.GetDefaultAudioStream(null); - var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options); + var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options); - var directPlayMethods = directPlayInfo.PlayMethods; + var directPlayMethod = directPlayInfo.PlayMethod; var transcodeReasons = directPlayInfo.TranscodeReasons; int? inputAudioChannels = audioStream?.Channels; @@ -297,55 +299,17 @@ namespace MediaBrowser.Model.Dlna int? inputAudioSampleRate = audioStream?.SampleRate; int? inputAudioBitDepth = audioStream?.BitDepth; - if (directPlayMethods.Any()) + if (directPlayMethod.HasValue) { - string audioCodec = audioStream?.Codec; - - // Make sure audio codec profiles are satisfied - var conditions = new List(); - foreach (var i in options.Profile.CodecProfiles) - { - if (i.Type == CodecType.Audio && i.ContainsAnyCodec(audioCodec, item.Container)) - { - bool applyConditions = true; - foreach (ProfileCondition applyCondition in i.ApplyConditions) - { - if (!ConditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth)) - { - LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item); - applyConditions = false; - break; - } - } - - if (applyConditions) - { - conditions.AddRange(i.Conditions); - } - } - } + var profile = options.Profile; + var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true); + var audioFailureReasons = AggregateFailureConditions(item, profile, "AudioCodecProfile", audioFailureConditions); + transcodeReasons |= audioFailureReasons; - bool all = true; - foreach (ProfileCondition c in conditions) + if (audioFailureReasons == TranscodeReason.None) { - if (!ConditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth)) - { - LogConditionFailure(options.Profile, "AudioCodecProfile", c, item); - transcodeReasons |= GetTranscodeReasonForFailedCondition(c); - - all = false; - break; - } - } - - if (all) - { - if (directPlayMethods.Contains(PlayMethod.DirectStream)) - { - playlistItem.PlayMethod = PlayMethod.DirectStream; - } - - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); + playlistItem.PlayMethod = directPlayMethod.Value; + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile); return playlistItem; } @@ -370,45 +334,9 @@ namespace MediaBrowser.Model.Dlna return null; } - SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile); - - var audioCodecProfiles = new List(); - foreach (var i in options.Profile.CodecProfiles) - { - if (i.Type == CodecType.Audio && i.ContainsAnyCodec(transcodingProfile.AudioCodec, transcodingProfile.Container)) - { - audioCodecProfiles.Add(i); - } - - if (audioCodecProfiles.Count >= 1) - { - break; - } - } - - var audioTranscodingConditions = new List(); - foreach (var i in audioCodecProfiles) - { - bool applyConditions = true; - foreach (var applyCondition in i.ApplyConditions) - { - if (!ConditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth)) - { - LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item); - applyConditions = false; - break; - } - } - - if (applyConditions) - { - foreach (ProfileCondition c in i.Conditions) - { - audioTranscodingConditions.Add(c); - } - } - } + SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); + var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcodingProfile.Container, transcodingProfile.AudioCodec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, false).ToArray(); ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true); // Honor requested max channels @@ -444,9 +372,9 @@ namespace MediaBrowser.Model.Dlna return options.GetMaxBitrate(isAudio); } - private (IEnumerable PlayMethods, TranscodeReason TranscodeReasons) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options) + private (DirectPlayProfile Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, AudioOptions options) { - DirectPlayProfile directPlayProfile = options.Profile.DirectPlayProfiles + var directPlayProfile = options.Profile.DirectPlayProfiles .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream)); if (directPlayProfile == null) @@ -457,20 +385,21 @@ namespace MediaBrowser.Model.Dlna item.Path ?? "Unknown path", audioStream.Codec ?? "Unknown codec"); - return (Enumerable.Empty(), GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); + return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); } var playMethods = new List(); var transcodeReasons = TranscodeReason.None; - // While options takes the network and other factors into account. Only applies to direct stream - if (item.SupportsDirectStream) + // The profile describes what the device supports + // If device requirements are satisfied then allow both direct stream and direct play + if (item.SupportsDirectPlay) { - if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) + if (IsItemBitrateEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay)) { - if (options.EnableDirectStream) + if (options.EnableDirectPlay) { - playMethods.Add(PlayMethod.DirectStream); + return (directPlayProfile, PlayMethod.DirectPlay, TranscodeReason.None); } } else @@ -479,15 +408,14 @@ namespace MediaBrowser.Model.Dlna } } - // The profile describes what the device supports - // If device requirements are satisfied then allow both direct stream and direct play - if (item.SupportsDirectPlay) + // While options takes the network and other factors into account. Only applies to direct stream + if (item.SupportsDirectStream) { - if (IsItemBitrateEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay)) + if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) { - if (options.EnableDirectPlay) + if (options.EnableDirectStream) { - playMethods.Add(PlayMethod.DirectPlay); + return (directPlayProfile, PlayMethod.DirectStream, transcodeReasons); } } else @@ -496,12 +424,7 @@ namespace MediaBrowser.Model.Dlna } } - if (playMethods.Count > 0) - { - transcodeReasons = TranscodeReason.None; - } - - return (playMethods, transcodeReasons); + return (directPlayProfile, null, transcodeReasons); } private static TranscodeReason GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable directPlayProfiles) @@ -592,30 +515,29 @@ namespace MediaBrowser.Model.Dlna return item.DefaultSubtitleStreamIndex; } - private static void SetStreamInfoOptionsFromTranscodingProfile(StreamInfo playlistItem, TranscodingProfile transcodingProfile) + private static void SetStreamInfoOptionsFromTranscodingProfile(MediaSourceInfo item, StreamInfo playlistItem, TranscodingProfile transcodingProfile) { - if (string.IsNullOrEmpty(transcodingProfile.AudioCodec)) - { - playlistItem.AudioCodecs = Array.Empty(); - } - else - { - playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(','); - } + var container = transcodingProfile.Container; + var protocol = transcodingProfile.Protocol; - playlistItem.Container = transcodingProfile.Container; - playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength; - playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; + item.TranscodingContainer = container; + item.TranscodingSubProtocol = protocol; - if (string.IsNullOrEmpty(transcodingProfile.VideoCodec)) + if (playlistItem.PlayMethod == PlayMethod.Transcode) { - playlistItem.VideoCodecs = Array.Empty(); + playlistItem.Container = container; + playlistItem.SubProtocol = protocol; } - else + + playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; + if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels) + && int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out int transcodingMaxAudioChannels)) { - playlistItem.VideoCodecs = transcodingProfile.VideoCodec.Split(','); + playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels; } + playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength; + playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps; playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; @@ -631,14 +553,21 @@ namespace MediaBrowser.Model.Dlna { playlistItem.SegmentLength = transcodingProfile.SegmentLength; } + } - playlistItem.SubProtocol = transcodingProfile.Protocol; + private static void SetStreamInfoOptionsFromDirectPlayProfile(VideoOptions options, MediaSourceInfo item, StreamInfo playlistItem, DirectPlayProfile directPlayProfile) + { + var container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile); + var protocol = "http"; - if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels) - && int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out int transcodingMaxAudioChannels)) - { - playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels; - } + item.TranscodingContainer = container; + item.TranscodingSubProtocol = protocol; + + playlistItem.Container = container; + playlistItem.SubProtocol = protocol; + + playlistItem.VideoCodecs = new[] { item.VideoStream.Codec }; + playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec); } private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options) @@ -667,11 +596,27 @@ namespace MediaBrowser.Model.Dlna playlistItem.AudioStreamIndex = audioStream.Index; } + // Collect candidate audio streams + IEnumerable candidateAudioStreams = audioStream == null ? Array.Empty() : new[] { audioStream }; + if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0) + { + if (audioStream?.IsDefault == true) + { + candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.IsDefault); + } + else + { + candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.Language == audioStream?.Language); + } + } + + candidateAudioStreams = candidateAudioStreams.ToArray(); + var videoStream = item.VideoStream; // TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough - var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectPlay); - var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectStream); + var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, options, PlayMethod.DirectPlay); + var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream); bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == TranscodeReason.None); bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == TranscodeReason.None); var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult; @@ -683,183 +628,292 @@ namespace MediaBrowser.Model.Dlna isEligibleForDirectPlay, isEligibleForDirectStream); + DirectPlayProfile directPlayProfile = null; if (isEligibleForDirectPlay || isEligibleForDirectStream) { // See if it can be direct played - var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectStream); + var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, candidateAudioStreams, subtitleStream, isEligibleForDirectPlay, isEligibleForDirectStream); var directPlay = directPlayInfo.PlayMethod; + transcodeReasons |= directPlayInfo.TranscodeReasons; if (directPlay != null) { + directPlayProfile = directPlayInfo.Profile; playlistItem.PlayMethod = directPlay.Value; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video); + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile); + playlistItem.VideoCodecs = new[] { videoStream.Codec }; + + if (directPlay == PlayMethod.DirectPlay) + { + playlistItem.SubProtocol = "http"; + + var audioStreamIndex = directPlayInfo.AudioStreamIndex ?? audioStream?.Index; + if (audioStreamIndex.HasValue) + { + playlistItem.AudioStreamIndex = audioStreamIndex; + playlistItem.AudioCodecs = new[] { item.GetMediaStream(MediaStreamType.Audio, audioStreamIndex.Value)?.Codec }; + } + } + else if (directPlay == PlayMethod.DirectStream) + { + playlistItem.AudioStreamIndex = audioStream?.Index; + if (audioStream != null) + { + playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec); + } + + SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile); + BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, directPlayProfile.Container, directPlayProfile.VideoCodec, directPlayProfile.AudioCodec); + } if (subtitleStream != null) { - var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, _transcoderSupport, item.Container, null); + var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, _transcoderSupport, directPlayProfile.Container, null); playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; } - - return playlistItem; } - transcodeReasons |= directPlayInfo.TranscodeReasons; + _logger.LogInformation( + "DirectPlay Result for Profile: {0}, Path: {1}, PlayMethod: {2}, AudioStreamIndex: {3}, SubtitleStreamIndex: {4}, Reasons: {5}", + options.Profile.Name ?? "Anonymous Profile", + item.Path ?? "Unknown path", + directPlayInfo.PlayMethod, + directPlayInfo.AudioStreamIndex ?? audioStream?.Index, + playlistItem.SubtitleStreamIndex, + directPlayInfo.TranscodeReasons); } - if (playlistItem.PlayMethod != PlayMethod.Transcode) + playlistItem.TranscodeReasons = transcodeReasons; + + if (playlistItem.PlayMethod != PlayMethod.DirectStream || !options.EnableDirectStream) { - playlistItem.TranscodeReasons = transcodeReasons; - return playlistItem; + // Can't direct play, find the transcoding profile + // If we do this for direct-stream we will overwrite the info + var transcodingProfile = GetVideoTranscodeProfile(item, options, videoStream, audioStream, candidateAudioStreams, subtitleStream, playlistItem); + if (transcodingProfile != null) + { + SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); + + BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, transcodingProfile.Container, transcodingProfile.VideoCodec, transcodingProfile.AudioCodec); + + if (subtitleStream != null) + { + var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol); + + playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; + playlistItem.SubtitleFormat = subtitleProfile.Format; + playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format }; + } + + if (playlistItem.PlayMethod != PlayMethod.DirectPlay) + { + playlistItem.PlayMethod = PlayMethod.Transcode; + } + } } - // Can't direct play, find the transcoding profile - TranscodingProfile transcodingProfile = null; - foreach (var i in options.Profile.TranscodingProfiles) + _logger.LogInformation( + "StreamBuilder.BuildVideoItem( Profile={0}, Path={1}, AudioStreamIndex={2}, SubtitleStreamIndex={3} ) => ( PlayMethod={4}, TranscodeReason={5} ) {6}", + options.Profile.Name ?? "Anonymous Profile", + item.Path ?? "Unknown path", + options.AudioStreamIndex, + options.SubtitleStreamIndex, + playlistItem.PlayMethod, + playlistItem.TranscodeReasons, + playlistItem.ToUrl("media:", "")); + + item.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile); + return playlistItem; + } + + private TranscodingProfile GetVideoTranscodeProfile(MediaSourceInfo item, VideoOptions options, MediaStream videoStream, MediaStream audioStream, IEnumerable candidateAudioStreams, MediaStream subtitleStream, StreamInfo playlistItem) + { + if (!(item.SupportsTranscoding || item.SupportsDirectStream)) + { + return null; + } + + var transcodingProfiles = options.Profile.TranscodingProfiles + .Where(i => i.Type == playlistItem.MediaType && i.Context == options.Context); + + if (options.AllowVideoStreamCopy) { - if (i.Type == playlistItem.MediaType && i.Context == options.Context) + // prefer direct copy profile + float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; + int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); + int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); + + transcodingProfiles = transcodingProfiles.ToLookup(transcodingProfile => { - transcodingProfile = i; - break; - } + var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec); + var match = ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec) && + options.Profile.CodecProfiles + .Any(i => i.Type == CodecType.Video && + i.ContainsAnyCodec(transcodingProfile.VideoCodec, transcodingProfile.Container) && + i.ApplyConditions.Any(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))); + + return match ? 1 : 2; + }) + .OrderBy(lookup => lookup.Key) + .SelectMany(lookup => lookup); } - if (transcodingProfile != null) + return transcodingProfiles.FirstOrDefault(); + } + + private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable candidateAudioStreams, string container, string videoCodec, string audioCodec) + { + // prefer matching video codecs + var videoCodecs = ContainerProfile.SplitValue(videoCodec); + var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream.Codec) ? videoStream.Codec : null; + playlistItem.VideoCodecs = directVideoCodec != null ? new[] { directVideoCodec } : videoCodecs; + + // copy video codec options as a starting point, this applies to transcode and direct-stream + playlistItem.MaxFramerate = videoStream.AverageFrameRate; + var qualifier = videoStream.Codec; + if (videoStream.Level.HasValue) { - if (!item.SupportsTranscoding) + playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (videoStream.BitDepth.HasValue) + { + playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (!string.IsNullOrEmpty(videoStream.Profile)) + { + playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant()); + } + + if (videoStream.Level != 0) + { + playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString()); + } + + // prefer matching audio codecs, could do beter here + var audioCodecs = ContainerProfile.SplitValue(audioCodec); + var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)); + playlistItem.AudioCodecs = audioCodecs; + if (directAudioStream != null) + { + audioStream = directAudioStream; + playlistItem.AudioStreamIndex = audioStream.Index; + playlistItem.AudioCodecs = new[] { audioStream.Codec }; + + // copy matching audio codec options + playlistItem.AudioSampleRate = audioStream.SampleRate; + playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString()); + + if (!string.IsNullOrEmpty(audioStream.Profile)) { - return null; + playlistItem.SetOption(audioStream.Codec, "profile", audioStream.Profile.ToLowerInvariant()); } - if (subtitleStream != null) + if (audioStream.Level != 0) { - var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol); - - playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; - playlistItem.SubtitleFormat = subtitleProfile.Format; - playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format }; + playlistItem.SetOption(audioStream.Codec, "level", audioStream.Level.ToString()); } + } - playlistItem.PlayMethod = PlayMethod.Transcode; + int? width = videoStream?.Width; + int? height = videoStream?.Height; + int? bitDepth = videoStream?.BitDepth; + int? videoBitrate = videoStream?.BitRate; + double? videoLevel = videoStream?.Level; + string videoProfile = videoStream?.Profile; + float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + bool? isAnamorphic = videoStream?.IsAnamorphic; + bool? isInterlaced = videoStream?.IsInterlaced; + string videoCodecTag = videoStream?.CodecTag; + bool? isAvc = videoStream?.IsAVC; + + TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; + int? packetLength = videoStream?.PacketLength; + int? refFrames = videoStream?.RefFrames; - SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile); + int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); + int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); - var isFirstAppliedCodecProfile = true; - foreach (var i in options.Profile.CodecProfiles) + var appliedVideoConditions = options.Profile.CodecProfiles + .Where(i => i.Type == CodecType.Video && + i.ContainsAnyCodec(videoCodec, container) && + i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))); + var isFirstAppliedCodecProfile = true; + foreach (var i in appliedVideoConditions) + { + var transcodingVideoCodecs = ContainerProfile.SplitValue(videoCodec); + foreach (var transcodingVideoCodec in transcodingVideoCodecs) { - if (i.Type == CodecType.Video && i.ContainsAnyCodec(transcodingProfile.VideoCodec, transcodingProfile.Container)) + if (i.ContainsAnyCodec(transcodingVideoCodec, container)) { - bool applyConditions = true; - foreach (ProfileCondition applyCondition in i.ApplyConditions) - { - int? width = videoStream?.Width; - int? height = videoStream?.Height; - int? bitDepth = videoStream?.BitDepth; - int? videoBitrate = videoStream?.BitRate; - double? videoLevel = videoStream?.Level; - string videoProfile = videoStream?.Profile; - float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; - bool? isAnamorphic = videoStream?.IsAnamorphic; - bool? isInterlaced = videoStream?.IsInterlaced; - string videoCodecTag = videoStream?.CodecTag; - bool? isAvc = videoStream?.IsAVC; - - TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; - int? packetLength = videoStream?.PacketLength; - int? refFrames = videoStream?.RefFrames; - - int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); - int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); - - if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) - { - // LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item); - applyConditions = false; - break; - } - } - - if (applyConditions) - { - var transcodingVideoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec); - foreach (var transcodingVideoCodec in transcodingVideoCodecs) - { - if (i.ContainsAnyCodec(transcodingVideoCodec, transcodingProfile.Container)) - { - ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, isFirstAppliedCodecProfile); - isFirstAppliedCodecProfile = false; - } - } - } + ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, isFirstAppliedCodecProfile); + isFirstAppliedCodecProfile = false; + continue; } } + } - // Honor requested max channels - playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; + // Honor requested max channels + playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; - int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem); - playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate); + int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem); + playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate); - isFirstAppliedCodecProfile = true; - foreach (var i in options.Profile.CodecProfiles) + bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream); + int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate; + int? audioChannels = audioStream == null ? null : audioStream.Channels; + string audioProfile = audioStream == null ? null : audioStream.Profile; + int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate; + int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth; + + var appliedAudioConditions = options.Profile.CodecProfiles + .Where(i => i.Type == CodecType.Video && + i.ContainsAnyCodec(audioCodec, container) && + i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))); + isFirstAppliedCodecProfile = true; + foreach (var i in appliedAudioConditions) + { + var transcodingAudioCodecs = ContainerProfile.SplitValue(audioCodec); + foreach (var transcodingAudioCodec in transcodingAudioCodecs) { - if (i.Type == CodecType.VideoAudio && i.ContainsAnyCodec(transcodingProfile.AudioCodec, transcodingProfile.Container)) + if (i.ContainsAnyCodec(transcodingAudioCodec, container)) { - bool applyConditions = true; - foreach (ProfileCondition applyCondition in i.ApplyConditions) - { - bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream); - int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate; - int? audioChannels = audioStream == null ? null : audioStream.Channels; - string audioProfile = audioStream == null ? null : audioStream.Profile; - int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate; - int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth; - - if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)) - { - // LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item); - applyConditions = false; - break; - } - } - - if (applyConditions) - { - var transcodingAudioCodecs = ContainerProfile.SplitValue(transcodingProfile.AudioCodec); - foreach (var transcodingAudioCodec in transcodingAudioCodecs) - { - if (i.ContainsAnyCodec(transcodingAudioCodec, transcodingProfile.Container)) - { - ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile); - isFirstAppliedCodecProfile = false; - } - } - } + ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile); + isFirstAppliedCodecProfile = false; + break; } } + } - var maxBitrateSetting = options.GetMaxBitrate(false); - // Honor max rate - if (maxBitrateSetting.HasValue) - { - var availableBitrateForVideo = maxBitrateSetting.Value; - - if (playlistItem.AudioBitrate.HasValue) - { - availableBitrateForVideo -= playlistItem.AudioBitrate.Value; - } + var maxBitrateSetting = options.GetMaxBitrate(false); + // Honor max rate + if (maxBitrateSetting.HasValue) + { + var availableBitrateForVideo = maxBitrateSetting.Value; - // Make sure the video bitrate is lower than bitrate settings but at least 64k - long currentValue = playlistItem.VideoBitrate ?? availableBitrateForVideo; - var longBitrate = Math.Max(Math.Min(availableBitrateForVideo, currentValue), 64000); - playlistItem.VideoBitrate = longBitrate >= int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); + if (playlistItem.AudioBitrate.HasValue) + { + availableBitrateForVideo -= playlistItem.AudioBitrate.Value; } - } - playlistItem.TranscodeReasons = transcodeReasons; + // Make sure the video bitrate is lower than bitrate settings but at least 64k + long currentValue = playlistItem.VideoBitrate ?? availableBitrateForVideo; + var longBitrate = Math.Max(Math.Min(availableBitrateForVideo, currentValue), 64000); + playlistItem.VideoBitrate = longBitrate >= int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); + } - return playlistItem; + _logger.LogInformation( + "Transcode Result for Profile: {0}, Path: {1}, PlayMethod: {2}, AudioStreamIndex: {3}, SubtitleStreamIndex: {4}, Reasons: {5}", + options.Profile.Name ?? "Anonymous Profile", + item.Path ?? "Unknown path", + playlistItem.PlayMethod, + audioStream.Index, + playlistItem.SubtitleStreamIndex, + playlistItem.TranscodeReasons); } private static int GetDefaultAudioBitrate(string audioCodec, int? audioChannels) @@ -988,63 +1042,30 @@ namespace MediaBrowser.Model.Dlna return 7168000; } - private (PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile( + private (DirectPlayProfile Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile( VideoOptions options, MediaSourceInfo mediaSource, MediaStream videoStream, MediaStream audioStream, + IEnumerable candidateAudioStreams, + MediaStream subtitleStream, + bool isEligibleForDirectPlay, bool isEligibleForDirectStream) { if (options.ForceDirectPlay) { - return (PlayMethod.DirectPlay, TranscodeReason.None); + return (null, PlayMethod.DirectPlay, audioStream?.Index, TranscodeReason.None); } if (options.ForceDirectStream) { - return (PlayMethod.DirectStream, TranscodeReason.None); + return (null, PlayMethod.DirectStream, audioStream?.Index, TranscodeReason.None); } DeviceProfile profile = options.Profile; string container = mediaSource.Container; - // See if it can be direct played - DirectPlayProfile directPlay = null; - foreach (var p in profile.DirectPlayProfiles) - { - if (p.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(p, container, videoStream, audioStream)) - { - directPlay = p; - break; - } - } - - if (directPlay == null) - { - _logger.LogDebug( - "Container: {Container}, Video: {Video}, Audio: {Audio} cannot be direct played by profile: {Profile} for path: {Path}", - container, - videoStream?.Codec ?? "no video", - audioStream?.Codec ?? "no audio", - profile.Name ?? "unknown profile", - mediaSource.Path ?? "unknown path"); - - return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles)); - } - - var conditions = new List(); - foreach (var p in profile.ContainerProfiles) - { - if (p.Type == DlnaProfileType.Video - && p.ContainsContainer(container)) - { - foreach (var c in p.Conditions) - { - conditions.Add(c); - } - } - } - + // video int? width = videoStream?.Width; int? height = videoStream?.Height; int? bitDepth = videoStream?.BitDepth; @@ -1056,12 +1077,9 @@ namespace MediaBrowser.Model.Dlna bool? isInterlaced = videoStream?.IsInterlaced; string videoCodecTag = videoStream?.CodecTag; bool? isAvc = videoStream?.IsAVC; - - int? audioBitrate = audioStream?.BitRate; - int? audioChannels = audioStream?.Channels; - string audioProfile = audioStream?.Profile; - int? audioSampleRate = audioStream?.SampleRate; - int? audioBitDepth = audioStream?.BitDepth; + // audio + var defaultLanguage = audioStream?.Language ?? string.Empty; + var defaultMarked = audioStream?.IsDefault ?? false; TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp; int? packetLength = videoStream?.PacketLength; @@ -1070,106 +1088,165 @@ namespace MediaBrowser.Model.Dlna int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio); int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video); - // Check container conditions - foreach (ProfileCondition i in conditions) - { - if (!ConditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) - { - LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource); - - var transcodeReasons = GetTranscodeReasonForFailedCondition(i); - return (null, transcodeReasons); - } - } - - string videoCodec = videoStream?.Codec; + var checkVideoConditions = (ProfileCondition[] conditions) => + conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)); - conditions = new List(); - foreach (var i in profile.CodecProfiles) - { - if (i.Type == CodecType.Video && i.ContainsAnyCodec(videoCodec, container)) - { - bool applyConditions = true; - foreach (ProfileCondition applyCondition in i.ApplyConditions) + // Check container conditions + var containerProfileReasons = AggregateFailureConditions( + mediaSource, + profile, + "VideoCodecProfile", + profile.ContainerProfiles + .Where(containerProfile => containerProfile.Type == DlnaProfileType.Video && containerProfile.ContainsContainer(container)) + .SelectMany(containerProfile => checkVideoConditions(containerProfile.Conditions))); + + // Check video conditions + var videoCodecProfileReasons = AggregateFailureConditions( + mediaSource, + profile, + "VideoCodecProfile", + profile.CodecProfiles + .Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container)) + .SelectMany(codecProfile => { - if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) + var failedApplyConditions = checkVideoConditions(codecProfile.ApplyConditions); + if (!failedApplyConditions.Any()) { - // LogConditionFailure(profile, "VideoCodecProfile.ApplyConditions", applyCondition, mediaSource); - applyConditions = false; - break; + return Array.Empty(); } - } - if (applyConditions) - { - foreach (ProfileCondition c in i.Conditions) - { - conditions.Add(c); - } - } - } - } + var failedConditions = checkVideoConditions(codecProfile.Conditions); + return failedApplyConditions.Concat(failedConditions); + })); + + // Check audiocandidates profile conditions + var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked)); - foreach (ProfileCondition i in conditions) + TranscodeReason subtitleProfileReasons = TranscodeReason.None; + if (subtitleStream != null) { - if (!ConditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) - { - LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource); + var subtitleProfile = GetSubtitleProfile(mediaSource, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.DirectPlay, _transcoderSupport, container, null); - var transcodeReason = GetTranscodeReasonForFailedCondition(i); - return (null, transcodeReason); + if (subtitleProfile.Method != SubtitleDeliveryMethod.Drop + && subtitleProfile.Method != SubtitleDeliveryMethod.External + && subtitleProfile.Method != SubtitleDeliveryMethod.Embed) + { + _logger.LogDebug("Not eligible for {0} due to unsupported subtitles", PlayMethod.DirectPlay); + subtitleProfileReasons |= TranscodeReason.SubtitleCodecNotSupported; } } - if (audioStream != null) - { - string audioCodec = audioStream.Codec; - conditions = new List(); - bool? isSecondaryAudio = mediaSource.IsSecondaryAudio(audioStream); - - foreach (var i in profile.CodecProfiles) + var rankings = new[] { VideoReasons, AudioReasons, ContainerReasons }; + var rank = (ref TranscodeReason a) => { - if (i.Type == CodecType.VideoAudio && i.ContainsAnyCodec(audioCodec, container)) + var index = 1; + foreach (var flag in rankings) { - bool applyConditions = true; - foreach (ProfileCondition applyCondition in i.ApplyConditions) + var reason = a & flag; + if (reason != TranscodeReason.None) { - if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio)) - { - // LogConditionFailure(profile, "VideoAudioCodecProfile.ApplyConditions", applyCondition, mediaSource); - applyConditions = false; - break; - } + a = reason; + return index; } - if (applyConditions) - { - foreach (ProfileCondition c in i.Conditions) - { - conditions.Add(c); - } - } + index++; } - } - foreach (ProfileCondition i in conditions) + return index; + }; + + // Check DirectPlay profiles to see if it can be direct played + var analyzedProfiles = profile.DirectPlayProfiles + .Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video) + .Select((directPlayProfile, order) => { - if (!ConditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio)) + var directPlayProfileReasons = TranscodeReason.None; + var audioCodecProfileReasons = TranscodeReason.None; + + // Check container type + if (!directPlayProfile.SupportsContainer(container)) { - LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource); + directPlayProfileReasons |= TranscodeReason.ContainerNotSupported; + } - var transcodeReasons = GetTranscodeReasonForFailedCondition(i); - return (null, transcodeReasons); + // Check video codec + string videoCodec = videoStream?.Codec; + if (!directPlayProfile.SupportsVideoCodec(videoCodec)) + { + directPlayProfileReasons |= TranscodeReason.VideoCodecNotSupported; } - } + + // Check audio codec + var selectedAudioStream = candidateAudioStreams.FirstOrDefault(audioStream => directPlayProfile.SupportsAudioCodec(audioStream.Codec)); + if (selectedAudioStream == null) + { + directPlayProfileReasons |= TranscodeReason.AudioCodecNotSupported; + } + else + { + audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream); + } + + var failureReasons = directPlayProfileReasons | containerProfileReasons | videoCodecProfileReasons | audioCodecProfileReasons | subtitleProfileReasons; + var directStreamFailureReasons = failureReasons & (~DirectStreamReasons); + + PlayMethod? playMethod = null; + if (failureReasons == TranscodeReason.None && isEligibleForDirectPlay && mediaSource.SupportsDirectPlay) + { + playMethod = PlayMethod.DirectPlay; + } + else if (directStreamFailureReasons == TranscodeReason.None && isEligibleForDirectStream && mediaSource.SupportsDirectStream && directPlayProfile != null) + { + playMethod = PlayMethod.DirectStream; + } + + var ranked = rank(ref failureReasons); + return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked); + }) + .OrderByDescending(analysis => analysis.Result.PlayMethod) + .ThenBy(analysis => analysis.Order) + .ToArray() + .ToLookup(analysis => analysis.Result.PlayMethod != null); + + var profileMatch = analyzedProfiles[true] + .Select(analysis => analysis.Result) + .FirstOrDefault(); + if (profileMatch.Profile != null) + { + return profileMatch; } - if (isEligibleForDirectStream && mediaSource.SupportsDirectStream) + var failureReasons = analyzedProfiles[false].OrderBy(a => a.Result.TranscodeReason).ThenBy(analysis => analysis.Order).FirstOrDefault().Result.TranscodeReason; + if (failureReasons == TranscodeReason.None) { - return (PlayMethod.DirectStream, TranscodeReason.None); + failureReasons = TranscodeReason.DirectPlayError; } - return (null, TranscodeReason.ContainerBitrateExceedsLimit); + return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons); + } + + private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream, string language, bool isDefault) + { + var profile = options.Profile; + var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, !audioStream.IsDefault); + + var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions); + if (audioStream?.IsExternal == true) + { + audioStreamFailureReasons |= TranscodeReason.AudioIsExternal; + } + + return audioStreamFailureReasons; + } + + private TranscodeReason AggregateFailureConditions(MediaSourceInfo mediaSource, DeviceProfile profile, string type, IEnumerable conditions) + { + return conditions.Aggregate(TranscodeReason.None, (reasons, i) => + { + LogConditionFailure(profile, type, i, mediaSource); + var transcodeReasons = GetTranscodeReasonForFailedCondition(i); + return reasons | transcodeReasons; + }); } private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource) @@ -1188,38 +1265,18 @@ namespace MediaBrowser.Model.Dlna private TranscodeReason IsEligibleForDirectPlay( MediaSourceInfo item, long maxBitrate, - MediaStream subtitleStream, - MediaStream audioStream, VideoOptions options, PlayMethod playMethod) { - var reason = TranscodeReason.None; - if (subtitleStream != null) - { - var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, playMethod, _transcoderSupport, item.Container, null); - - if (subtitleProfile.Method != SubtitleDeliveryMethod.Drop - && subtitleProfile.Method != SubtitleDeliveryMethod.External - && subtitleProfile.Method != SubtitleDeliveryMethod.Embed) - { - _logger.LogDebug("Not eligible for {0} due to unsupported subtitles", playMethod); - reason |= TranscodeReason.SubtitleCodecNotSupported; - } - } - bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod); if (!result) { - reason |= TranscodeReason.ContainerBitrateExceedsLimit; + return TranscodeReason.ContainerBitrateExceedsLimit; } - - // TODO:6450 support external audio in DirectStream? - if (audioStream?.IsExternal == true) + else { - reason |= TranscodeReason.AudioIsExternal; + return TranscodeReason.None; } - - return reason; } public static SubtitleProfile GetSubtitleProfile( @@ -1443,6 +1500,47 @@ namespace MediaBrowser.Model.Dlna } } + private static IEnumerable GetProfileConditionsForVideoAudio( + IEnumerable codecProfiles, + string container, + string codec, + int? audioChannels, + int? audioBitrate, + int? audioSampleRate, + int? audioBitDepth, + string audioProfile, + bool? isSecondaryAudio) + { + return codecProfiles + .Where(profile => profile.Type == CodecType.VideoAudio && profile.ContainsAnyCodec(codec, container) && + profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))) + .SelectMany(profile => profile.Conditions) + .Where(condition => !ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio)); + } + + private static IEnumerable GetProfileConditionsForAudio( + IEnumerable codecProfiles, + string container, + string codec, + int? audioChannels, + int? audioBitrate, + int? audioSampleRate, + int? audioBitDepth, + bool checkConditions) + { + var conditions = codecProfiles + .Where(profile => profile.Type == CodecType.Audio && profile.ContainsAnyCodec(codec, container) && + profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth))) + .SelectMany(profile => profile.Conditions); + + if (!checkConditions) + { + return conditions; + } + + return conditions.Where(condition => !ConditionProcessor.IsAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth)); + } + private void ApplyTranscodingConditions(StreamInfo item, IEnumerable conditions, string qualifier, bool enableQualifiedConditions, bool enableNonQualifiedConditions) { foreach (ProfileCondition condition in conditions) @@ -1722,10 +1820,22 @@ namespace MediaBrowser.Model.Dlna var values = value .Split('|', StringSplitOptions.RemoveEmptyEntries); - if (condition.Condition == ProfileConditionType.Equals || condition.Condition == ProfileConditionType.EqualsAny) + if (condition.Condition == ProfileConditionType.Equals) { item.SetOption(qualifier, "profile", string.Join(',', values)); } + else if (condition.Condition == ProfileConditionType.EqualsAny) + { + var currentValue = item.GetOption(qualifier, "profile"); + if (!string.IsNullOrEmpty(currentValue) && values.Any(value => value == currentValue)) + { + item.SetOption(qualifier, "profile", currentValue); + } + else + { + item.SetOption(qualifier, "profile", string.Join(',', values)); + } + } break; } @@ -1883,29 +1993,5 @@ namespace MediaBrowser.Model.Dlna return true; } - - private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, string container, MediaStream videoStream, MediaStream audioStream) - { - // Check container type - if (!profile.SupportsContainer(container)) - { - return false; - } - - // Check video codec - string videoCodec = videoStream?.Codec; - if (!profile.SupportsVideoCodec(videoCodec)) - { - return false; - } - - // Check audio codec - if (audioStream != null && !profile.SupportsAudioCodec(audioStream.Codec)) - { - return false; - } - - return true; - } } } diff --git a/MediaBrowser.Model/Dlna/VideoOptions.cs b/MediaBrowser.Model/Dlna/VideoOptions.cs index 4194f17c6e..0cb80af544 100644 --- a/MediaBrowser.Model/Dlna/VideoOptions.cs +++ b/MediaBrowser.Model/Dlna/VideoOptions.cs @@ -10,5 +10,7 @@ namespace MediaBrowser.Model.Dlna public int? AudioStreamIndex { get; set; } public int? SubtitleStreamIndex { get; set; } + + public bool AllowVideoStreamCopy { get; set; } } } diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 2281e6ae5d..bb98488480 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -161,7 +161,7 @@ namespace MediaBrowser.Model.Dto public MediaStream GetDefaultAudioStream(int? defaultIndex) { - if (defaultIndex.HasValue) + if (defaultIndex.HasValue && defaultIndex != -1) { var val = defaultIndex.Value; diff --git a/MediaBrowser.Model/Properties/AssemblyInfo.cs b/MediaBrowser.Model/Properties/AssemblyInfo.cs index e50baf604e..6bf1eb0c03 100644 --- a/MediaBrowser.Model/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Model/Properties/AssemblyInfo.cs @@ -16,6 +16,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] [assembly: InternalsVisibleTo("Jellyfin.Model.Tests")] +[assembly: InternalsVisibleTo("Jellyfin.Dlna.Tests")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs index c3570840f9..c99136384e 100644 --- a/MediaBrowser.Model/Session/TranscodeReason.cs +++ b/MediaBrowser.Model/Session/TranscodeReason.cs @@ -31,21 +31,16 @@ namespace MediaBrowser.Model.Session AudioChannelsNotSupported = 1 << 14, AudioProfileNotSupported = 1 << 15, AudioSampleRateNotSupported = 1 << 16, - AudioBitDepthNotSupported = 1 << 20, + AudioBitDepthNotSupported = 1 << 17, // Bitrate Constraints - ContainerBitrateExceedsLimit = 1 << 17, - VideoBitrateNotSupported = 1 << 18, - AudioBitrateNotSupported = 1 << 19, + ContainerBitrateExceedsLimit = 1 << 18, + VideoBitrateNotSupported = 1 << 19, + AudioBitrateNotSupported = 1 << 20, // Errors - UnknownVideoStreamInfo = 1 << 20, - UnknownAudioStreamInfo = 1 << 21, - DirectPlayError = 1 << 22, - - // Aliases - ContainerReasons = ContainerNotSupported | ContainerBitrateExceedsLimit, - AudioReasons = AudioCodecNotSupported | AudioBitrateNotSupported | AudioChannelsNotSupported | AudioProfileNotSupported | AudioSampleRateNotSupported | SecondaryAudioNotSupported | AudioBitDepthNotSupported | AudioIsExternal, - VideoReasons = VideoCodecNotSupported | VideoResolutionNotSupported | AnamorphicVideoNotSupported | InterlacedVideoNotSupported | VideoBitDepthNotSupported | VideoBitrateNotSupported | VideoFramerateNotSupported | VideoLevelNotSupported | RefFramesNotSupported, + UnknownVideoStreamInfo = 1 << 21, + UnknownAudioStreamInfo = 1 << 22, + DirectPlayError = 1 << 23, } } diff --git a/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs b/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs index c7a5095f74..213257b209 100644 --- a/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs +++ b/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs @@ -1,22 +1,34 @@ -#pragma warning disable CS1591 - using System; using System.Linq; namespace MediaBrowser.Model.Session { + /// + /// Extension methods for serializing TranscodeReason. + /// public static class TranscodeReasonExtensions { - private static TranscodeReason[] values = Enum.GetValues(); + private static readonly TranscodeReason[] _values = Enum.GetValues(); - public static string Serialize(this MediaBrowser.Model.Session.TranscodeReason reasons, string sep = ",") + /// + /// Serializes a TranscodeReason into a delimiter-separated string. + /// + /// The enumeration. + /// The string separator to use. defualt ,. + /// string of transcode reasons delimited. + public static string Serialize(this TranscodeReason reasons, string sep = ",") { return string.Join(sep, reasons.ToArray()); } - public static TranscodeReason[] ToArray(this MediaBrowser.Model.Session.TranscodeReason reasons) + /// + /// Serializes a TranscodeReason into an array of individual TranscodeReason bits. + /// + /// The enumeration. + /// Array of TranscodeReason. + public static TranscodeReason[] ToArray(this TranscodeReason reasons) { - return values.Where(r => r != 0 && reasons.HasFlag(r)).ToArray(); + return _values.Where(r => r != 0 && reasons.HasFlag(r)).ToArray(); } } } diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index 78e5baad7e..8834856b04 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/MediaBrowser.Model/Session/TranscodingInfo.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Model.Session public HardwareEncodingType? HardwareAccelerationType { get; set; } - public TranscodeReason[] TranscodeReasons { get => TranscodeReason.ToArray(); } + public TranscodeReason[] TranscodeReasons => TranscodeReason.ToArray(); [JsonIgnore] public TranscodeReason TranscodeReason { get; set; } diff --git a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs index ccd95f7502..c3e3324bbd 100644 --- a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs +++ b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs @@ -2,6 +2,7 @@ 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; @@ -19,97 +20,117 @@ namespace Jellyfin.MediaBrowser.Model.Tests { [Theory] // Chrome - [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay - [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream + [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 | TranscodeReason.AudioCodecNotSupported, "Transcode")] - [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' - [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' - [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [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.None, "Remux")] // #6450 // Firefox - [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay - [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream + [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 | TranscodeReason.AudioCodecNotSupported, "Transcode")] - [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' - [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' - [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [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.None, "Remux")] // #6450 // Safari - [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("SafariNext", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay - [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay + [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.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", PlayMethod.DirectPlay)] // #6450 // AndroidPixel - [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [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.DirectStream)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] - [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] + [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 // RokuSSPlus - [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream - [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectStream + [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 should be DirectPlay + [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 // JellyfinMediaPlayer - [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "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.None, "Remux")] // #6450 // TranscodeMedia - [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay - [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] + [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] + [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] + [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] + [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] + [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] + [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] + [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] // DirectMedia - [InlineData("DirectMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("DirectMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("DirectMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("DirectMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("DirectMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("DirectMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("DirectMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [InlineData("DirectMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] + [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] + [InlineData("DirectMedia", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] + [InlineData("DirectMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] + [InlineData("DirectMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] + [InlineData("DirectMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "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)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay - [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay + [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 | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Null", "mp4-h264-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Null", "mp4-hevc-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay + [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)] + + // [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") { var options = await GetVideoOptions(deviceName, mediaSource); @@ -118,88 +139,103 @@ namespace Jellyfin.MediaBrowser.Model.Tests [Theory] // Chrome - [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream + [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 + [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 | TranscodeReason.AudioCodecNotSupported, "Transcode")] - [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' - [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' - [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [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.None, "Remux")] // #6450 // Firefox - [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream + [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 | TranscodeReason.AudioCodecNotSupported, "Transcode")] - [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' - [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false' - [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [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.None, "Remux")] // #6450 // Safari - [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay - [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay + [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.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)] // #6450 + [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", PlayMethod.DirectPlay)] // #6450 // AndroidPixel - [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay + [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.DirectStream)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] - [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "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.None, "Remux")] // #6450 + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 // RokuSSPlus - [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream - [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectStream + [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "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.None, "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.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod?playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") + [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "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.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") { var options = await GetVideoOptions(deviceName, mediaSource); options.AudioStreamIndex = 1; - options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count() - 1; - BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); + 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.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay - [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450 should have container & profile video reasons? + [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.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay - [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450 should have container & profile video reasons? + [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.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay - [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported, "Transcode")] // #6450 should be DirectPlay + [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 // RokuSSPlus - [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay - public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") + [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") { var options = await GetVideoOptions(deviceName, mediaSource); - var streamCount = options.MediaSources[0].MediaStreams.Count(); + var streamCount = options.MediaSources[0].MediaStreams.Count; options.AudioStreamIndex = streamCount - 2; options.SubtitleStreamIndex = streamCount - 1; - BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); + + var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); + Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex); + Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex); } - private void BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol) + private StreamInfo? BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol) { if (string.IsNullOrEmpty(transcodeProtocol)) { @@ -235,7 +271,8 @@ namespace Jellyfin.MediaBrowser.Model.Tests { // check expected container var containers = ContainerProfile.SplitValue(mediaSource.Container); - Assert.Contains(uri.Extension, containers); + // TODO: test transcode too + // Assert.Contains(uri.Extension, containers); // check expected video codec (1) Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); @@ -243,15 +280,19 @@ namespace Jellyfin.MediaBrowser.Model.Tests // check expected audio codecs (1) Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec); - Assert.Single(val.AudioCodecs); + Assert.Single(val.TargetAudioCodec); + // Assert.Single(val.AudioCodecs); - // TODO: validate transcoding options as well + 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); + Assert.NotEmpty(val.VideoCodecs); + Assert.NotEmpty(val.AudioCodecs); // check expected container (todo: this could be a test param) if (transcodeProtocol == "http") @@ -259,7 +300,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests // Assert.Equal("webm", val.Container); Assert.Equal(val.Container, uri.Extension); Assert.Equal("stream", uri.Filename); - // Assert.Equal("http", val.SubProtocol); + Assert.Equal("http", val.SubProtocol); } else { @@ -272,12 +313,11 @@ namespace Jellyfin.MediaBrowser.Model.Tests // Full transcode if (transcodeMode == "Transcode") { - // TODO: what else to validate here - if ((val.TranscodeReasons & TranscodeReason.ContainerReasons) == TranscodeReason.None) + if ((val.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == TranscodeReason.None) { - // Assert.All( - // videoStreams, - // stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs)); + Assert.All( + videoStreams, + stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs)); } // todo: fill out tests here @@ -295,7 +335,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests if (!targetAudioStream.IsExternal) { // check expected audio codecs (1) - // Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs); + Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs); } } else if (transcodeMode == "Remux") @@ -309,10 +349,10 @@ namespace Jellyfin.MediaBrowser.Model.Tests 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()) ?? new string[0]); - // Assert.Equal(videoStream.Level, val.TargetVideoLevel); - // Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth); - // Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue); + Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, val.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty()); + 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) != TranscodeReason.None) @@ -335,24 +375,23 @@ namespace Jellyfin.MediaBrowser.Model.Tests { if (!stream.IsExternal) { - // Assert.DoesNotContain(stream.Codec, val.AudioCodecs); + Assert.DoesNotContain(stream.Codec, val.AudioCodecs); } }); } } } } - - if (playMethod == null) + else if (playMethod == null) { - // what should the actual result be here? Assert.Null(val.SubProtocol); - Assert.EndsWith("/stream", uri.Path, StringComparison.InvariantCulture); + Assert.Equal("stream", uri.Filename); Assert.False(val.EstimateContentLength); Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); - // Assert.True(val.CopyTimestamps); } + + return val; } private static async ValueTask TestData(string name) @@ -366,7 +405,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests return value; } - throw new Exception("Invalid test data: " + name); + throw new SerializationException("Invalid test data: " + name); } } @@ -394,6 +433,8 @@ namespace Jellyfin.MediaBrowser.Model.Tests MediaSources = mediaSources, DeviceId = "test-deviceId", Profile = dp, + AllowAudioStreamCopy = true, + AllowVideoStreamCopy = true, }; } From 84a3db6f84bab04ee85141b3e923713e58b4bde6 Mon Sep 17 00:00:00 2001 From: Isaac Gordezky Date: Sat, 19 Feb 2022 17:02:41 +0000 Subject: [PATCH 04/10] Fix transcode video matching and add tests for Transcode and Safari --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 19 +++-- .../Jellyfin.Dlna.Tests/StreamBuilderTests.cs | 41 ++++++----- .../Test Data/DeviceProfile-SafariNext.json | 60 ++++++--------- .../DeviceProfile-TranscodeMedia.json | 38 +++++++--- ...MediaSourceInfo-mkv-av1-aac-srt-2600k.json | 73 +++++++++++++++++++ ...iaSourceInfo-mkv-av1-vorbis-srt-2600k.json | 72 ++++++++++++++++++ 6 files changed, 232 insertions(+), 71 deletions(-) create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json create mode 100644 tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 0f48e985d5..8001e2efbb 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -749,13 +749,20 @@ namespace MediaBrowser.Model.Dlna transcodingProfiles = transcodingProfiles.ToLookup(transcodingProfile => { var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec); - var match = ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec) && - options.Profile.CodecProfiles - .Any(i => i.Type == CodecType.Video && - i.ContainsAnyCodec(transcodingProfile.VideoCodec, transcodingProfile.Container) && - i.ApplyConditions.Any(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))); - return match ? 1 : 2; + if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec)) { + var videoCodec = transcodingProfile.VideoCodec; + var container = transcodingProfile.Container; + var appliedVideoConditions = options.Profile.CodecProfiles + .Where(i => i.Type == CodecType.Video && + i.ContainsAnyCodec(videoCodec, container)) + .Select(i => + i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))); + var conditionsSatisfied = !appliedVideoConditions.Any() || !appliedVideoConditions.Any(satisfied => !satisfied); + return conditionsSatisfied ? 1 : 2; + } + + return 3; }) .OrderBy(lookup => lookup.Key) .SelectMany(lookup => lookup); diff --git a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs index c3e3324bbd..cc9a2f12ff 100644 --- a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs +++ b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs @@ -47,9 +47,9 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.DirectPlay)] // #6450 - [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)] // #6450 - [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", 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 @@ -93,14 +93,16 @@ namespace Jellyfin.MediaBrowser.Model.Tests [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 // TranscodeMedia - [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] - [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] - [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] - [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] - [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] - [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] - [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] - [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")] + [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.None, "Remux")] [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] @@ -129,8 +131,6 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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)] - - // [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") { var options = await GetVideoOptions(deviceName, mediaSource); @@ -165,9 +165,9 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.DirectPlay)] // #6450 - [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)] // #6450 - [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", 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 @@ -239,7 +239,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests { if (string.IsNullOrEmpty(transcodeProtocol)) { - transcodeProtocol = playMethod == PlayMethod.DirectStream ? "http" : "hls"; + transcodeProtocol = playMethod == PlayMethod.DirectStream ? "http" : "HLS.ts"; } var builder = GetStreamBuilder(); @@ -302,6 +302,13 @@ namespace Jellyfin.MediaBrowser.Model.Tests 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); diff --git a/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json index 8412d3e9bf..3b5a0c2549 100644 --- a/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json +++ b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json @@ -24,14 +24,14 @@ { "Container": "mp4,m4v", "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis", - "VideoCodec": "hevc,h264,vp8,vp9", + "VideoCodec": "h264,vp8,vp9", "Type": "Video", "$type": "DirectPlayProfile" }, { "Container": "mov", "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis", - "VideoCodec": "hevc,h264", + "VideoCodec": "h264", "Type": "Video", "$type": "DirectPlayProfile" }, @@ -217,57 +217,43 @@ "$type": "TranscodingProfile" }, { - "Container": "ts", + "Container": "mp4", "Type": "Video", + "AudioCodec": "aac,ac3,eac3,flac,alac", "VideoCodec": "hevc,h264", - "AudioCodec": "aac,mp3,ac3,eac3", + "Context": "Streaming", "Protocol": "hls", - "EstimateContentLength": false, - "EnableMpegtsM2TsMode": false, - "TranscodeSeekInfo": "Auto", - "CopyTimestamps": false, + "MaxAudioChannels": "2", + "MinSegments": "2", + "BreakOnNonKeyFrames": true + }, + { + "Container": "ts", + "Type": "Video", + "AudioCodec": "aac,mp3,ac3,eac3", + "VideoCodec": "h264", "Context": "Streaming", - "EnableSubtitlesInManifest": false, - "MaxAudioChannels": "6", - "MinSegments": 2, - "SegmentLength": 0, - "BreakOnNonKeyFrames": true, - "$type": "TranscodingProfile" + "Protocol": "hls", + "MaxAudioChannels": "2", + "MinSegments": "2", + "BreakOnNonKeyFrames": true }, { "Container": "webm", "Type": "Video", - "VideoCodec": "vp8,vp9,vpx", "AudioCodec": "vorbis", - "Protocol": "http", - "EstimateContentLength": false, - "EnableMpegtsM2TsMode": false, - "TranscodeSeekInfo": "Auto", - "CopyTimestamps": false, + "VideoCodec": "vp8,vpx", "Context": "Streaming", - "EnableSubtitlesInManifest": false, - "MaxAudioChannels": "6", - "MinSegments": 0, - "SegmentLength": 0, - "BreakOnNonKeyFrames": false, - "$type": "TranscodingProfile" + "Protocol": "http", + "MaxAudioChannels": "2" }, { "Container": "mp4", "Type": "Video", - "VideoCodec": "hevc,h264", "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis", - "Protocol": "http", - "EstimateContentLength": false, - "EnableMpegtsM2TsMode": false, - "TranscodeSeekInfo": "Auto", - "CopyTimestamps": false, + "VideoCodec": "h264", "Context": "Static", - "EnableSubtitlesInManifest": false, - "MinSegments": 0, - "SegmentLength": 0, - "BreakOnNonKeyFrames": false, - "$type": "TranscodingProfile" + "Protocol": "http" } ], "CodecProfiles": [ diff --git a/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json index 6fd1aaa24d..9fc1ae6bb2 100644 --- a/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json +++ b/tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json @@ -19,22 +19,38 @@ "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", - "VideoCodec": "h264,h265,hevc,mpeg4,mpeg2video", - "AudioCodec": "aac,mp3,ac3,opus,flac,vorbis", + "AudioCodec": "aac,mp3", + "VideoCodec": "h264", + "Context": "Streaming", "Protocol": "hls", - "EstimateContentLength": false, - "EnableMpegtsM2TsMode": false, - "TranscodeSeekInfo": "Auto", - "CopyTimestamps": false, + "MaxAudioChannels": "2", + "MinSegments": "2", + "BreakOnNonKeyFrames": true, + "$type": "TranscodingProfile" + }, + { + "Container": "webm", + "Type": "Video", + "AudioCodec": "vorbis", + "VideoCodec": "vp9,vp8,vpx,av1", "Context": "Streaming", - "EnableSubtitlesInManifest": false, - "MaxAudioChannels": "6", - "MinSegments": 0, - "SegmentLength": 0, - "BreakOnNonKeyFrames": false, + "Protocol": "http", + "MaxAudioChannels": "2", "$type": "TranscodingProfile" }, { diff --git a/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json new file mode 100644 index 0000000000..da185aacfb --- /dev/null +++ b/tests/Jellyfin.Dlna.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.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json b/tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json new file mode 100644 index 0000000000..774dba32ae --- /dev/null +++ b/tests/Jellyfin.Dlna.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 +} From 9ebd5217543c8c5d68abc0f20f24de62ed68af5d Mon Sep 17 00:00:00 2001 From: Isaac Gordezky Date: Mon, 21 Feb 2022 07:54:29 -0500 Subject: [PATCH 05/10] Update MediaBrowser.Model/Dlna/StreamBuilder.cs Co-authored-by: Cody Robibero --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 8001e2efbb..2fb5a123fc 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -750,7 +750,8 @@ namespace MediaBrowser.Model.Dlna { var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec); - if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec)) { + if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec)) + { var videoCodec = transcodingProfile.VideoCodec; var container = transcodingProfile.Container; var appliedVideoConditions = options.Profile.CodecProfiles From c331e11c24128bf8f969097afd342353e244ada1 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 5 Mar 2022 13:40:57 -0700 Subject: [PATCH 06/10] Clean up EnumFlags serialization --- .../Controllers/UniversalAudioController.cs | 4 +-- .../MediaEncoding/EncodingJobInfo.cs | 3 -- MediaBrowser.Model/Dlna/StreamBuilder.cs | 12 +++---- MediaBrowser.Model/Dlna/StreamInfo.cs | 2 +- .../Session/TranscodeReasonExtensions.cs | 34 ------------------ MediaBrowser.Model/Session/TranscodingInfo.cs | 5 --- .../Json/Converters/JsonFlagEnumConverter.cs | 36 +++++++++++++++++++ .../JsonFlagEnumConverterFactory.cs | 24 +++++++++++++ src/Jellyfin.Extensions/Json/JsonDefaults.cs | 1 + 9 files changed, 70 insertions(+), 51 deletions(-) delete mode 100644 MediaBrowser.Model/Session/TranscodeReasonExtensions.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverterFactory.cs diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 962f637d4e..cf08584869 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -224,7 +224,7 @@ namespace Jellyfin.Api.Controllers DeInterlace = false, RequireNonAnamorphic = false, EnableMpegtsM2TsMode = false, - TranscodeReasons = mediaSource.TranscodeReasons == MediaBrowser.Model.Session.TranscodeReason.None ? null : mediaSource.TranscodeReasons.Serialize(), + TranscodeReasons = mediaSource.TranscodeReasons == TranscodeReason.None ? null : mediaSource.TranscodeReasons.ToString(), Context = EncodingContext.Static, StreamOptions = new Dictionary(), EnableAdaptiveBitrateStreaming = true @@ -255,7 +255,7 @@ namespace Jellyfin.Api.Controllers CopyTimestamps = true, StartTimeTicks = startTimeTicks, SubtitleMethod = SubtitleDeliveryMethod.Embed, - TranscodeReasons = mediaSource.TranscodeReasons == MediaBrowser.Model.Session.TranscodeReason.None ? null : mediaSource.TranscodeReasons.Serialize(), + TranscodeReasons = mediaSource.TranscodeReasons == TranscodeReason.None ? null : mediaSource.TranscodeReasons.ToString(), Context = EncodingContext.Static }; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 23067c7b18..d95461497b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -35,9 +35,6 @@ namespace MediaBrowser.Controller.MediaEncoding SupportedSubtitleCodecs = Array.Empty(); } - public TranscodeReason[] TranscodeReasons => TranscodeReason.ToArray(); - - [JsonIgnore] public TranscodeReason TranscodeReason { get diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 2fb5a123fc..b038d8ff1a 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -915,13 +915,13 @@ namespace MediaBrowser.Model.Dlna } _logger.LogInformation( - "Transcode Result for Profile: {0}, Path: {1}, PlayMethod: {2}, AudioStreamIndex: {3}, SubtitleStreamIndex: {4}, Reasons: {5}", - options.Profile.Name ?? "Anonymous Profile", + "Transcode Result for Profile: {Profile}, Path: {Path}, PlayMethod: {PlayMethod}, AudioStreamIndex: {AudioStreamIndex}, SubtitleStreamIndex: {SubtitleStreamIndex}, Reasons: {TranscodeReason}", + options.Profile?.Name ?? "Anonymous Profile", item.Path ?? "Unknown path", - playlistItem.PlayMethod, - audioStream.Index, - playlistItem.SubtitleStreamIndex, - playlistItem.TranscodeReasons); + playlistItem?.PlayMethod, + audioStream?.Index, + playlistItem?.SubtitleStreamIndex, + playlistItem?.TranscodeReasons); } private static int GetDefaultAudioBitrate(string audioCodec, int? audioChannels) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 3b86d5f42c..79dfff5c24 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -798,7 +798,7 @@ namespace MediaBrowser.Model.Dlna if (!item.IsDirectStream) { - list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.Serialize())); + list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString())); } return list; diff --git a/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs b/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs deleted file mode 100644 index 213257b209..0000000000 --- a/MediaBrowser.Model/Session/TranscodeReasonExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Linq; - -namespace MediaBrowser.Model.Session -{ - /// - /// Extension methods for serializing TranscodeReason. - /// - public static class TranscodeReasonExtensions - { - private static readonly TranscodeReason[] _values = Enum.GetValues(); - - /// - /// Serializes a TranscodeReason into a delimiter-separated string. - /// - /// The enumeration. - /// The string separator to use. defualt ,. - /// string of transcode reasons delimited. - public static string Serialize(this TranscodeReason reasons, string sep = ",") - { - return string.Join(sep, reasons.ToArray()); - } - - /// - /// Serializes a TranscodeReason into an array of individual TranscodeReason bits. - /// - /// The enumeration. - /// Array of TranscodeReason. - public static TranscodeReason[] ToArray(this TranscodeReason reasons) - { - return _values.Where(r => r != 0 && reasons.HasFlag(r)).ToArray(); - } - } -} diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index 8834856b04..f876fa9614 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/MediaBrowser.Model/Session/TranscodingInfo.cs @@ -1,8 +1,6 @@ #nullable disable #pragma warning disable CS1591 -using System.Text.Json.Serialization; - namespace MediaBrowser.Model.Session { public class TranscodingInfo @@ -31,9 +29,6 @@ namespace MediaBrowser.Model.Session public HardwareEncodingType? HardwareAccelerationType { get; set; } - public TranscodeReason[] TranscodeReasons => TranscodeReason.ToArray(); - - [JsonIgnore] public TranscodeReason TranscodeReason { get; set; } } } diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs new file mode 100644 index 0000000000..6e4c85c474 --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters; + +/// +/// Enum flag to json array converter. +/// +/// The type of enum. +public class JsonFlagEnumConverter : JsonConverter + where T : Enum +{ + private static readonly T[] _enumValues = (T[])Enum.GetValues(typeof(T)); + + /// + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + /// + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + foreach (var enumValue in _enumValues) + { + if (value.HasFlag(enumValue)) + { + writer.WriteStringValue(enumValue.ToString()); + } + } + + writer.WriteEndArray(); + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverterFactory.cs b/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverterFactory.cs new file mode 100644 index 0000000000..b74caf345d --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverterFactory.cs @@ -0,0 +1,24 @@ +using System; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters; + +/// +/// Json flag enum converter factory. +/// +public class JsonFlagEnumConverterFactory : JsonConverterFactory +{ + /// + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsEnum && typeToConvert.IsDefined(typeof(FlagsAttribute)); + } + + /// + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + return (JsonConverter?)Activator.CreateInstance(typeof(JsonFlagEnumConverter<>).MakeGenericType(typeToConvert)); + } +} diff --git a/src/Jellyfin.Extensions/Json/JsonDefaults.cs b/src/Jellyfin.Extensions/Json/JsonDefaults.cs index 2cd89dc3bc..97cbee9710 100644 --- a/src/Jellyfin.Extensions/Json/JsonDefaults.cs +++ b/src/Jellyfin.Extensions/Json/JsonDefaults.cs @@ -36,6 +36,7 @@ namespace Jellyfin.Extensions.Json new JsonGuidConverter(), new JsonNullableGuidConverter(), new JsonVersionConverter(), + new JsonFlagEnumConverterFactory(), new JsonStringEnumConverter(), new JsonNullableStructConverterFactory(), new JsonBoolNumberConverter(), From bbd5d11d3b62f0edc1456a8116753ea0fbaec04f Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 5 Mar 2022 13:58:21 -0700 Subject: [PATCH 07/10] Remove TranscodeReason.None, Add JsonFlagEnum tests --- .../Controllers/UniversalAudioController.cs | 4 +- .../MediaEncoding/EncodingJobInfo.cs | 4 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 48 +++++------ MediaBrowser.Model/Session/TranscodeReason.cs | 2 - .../Jellyfin.Dlna.Tests/StreamBuilderTests.cs | 80 +++++++++---------- .../Json/Converters/JsonFlagEnumTests.cs | 28 +++++++ 6 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonFlagEnumTests.cs diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index cf08584869..6fcafd426c 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -224,7 +224,7 @@ namespace Jellyfin.Api.Controllers DeInterlace = false, RequireNonAnamorphic = false, EnableMpegtsM2TsMode = false, - TranscodeReasons = mediaSource.TranscodeReasons == TranscodeReason.None ? null : mediaSource.TranscodeReasons.ToString(), + TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(), Context = EncodingContext.Static, StreamOptions = new Dictionary(), EnableAdaptiveBitrateStreaming = true @@ -255,7 +255,7 @@ namespace Jellyfin.Api.Controllers CopyTimestamps = true, StartTimeTicks = startTimeTicks, SubtitleMethod = SubtitleDeliveryMethod.Embed, - TranscodeReasons = mediaSource.TranscodeReasons == TranscodeReason.None ? null : mediaSource.TranscodeReasons.ToString(), + TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(), Context = EncodingContext.Static }; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index d95461497b..4f67435908 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -43,8 +43,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.TranscodeReasons == null) { - _transcodeReasons = TranscodeReason.None; - return TranscodeReason.None; + _transcodeReasons = 0; + return 0; } _ = Enum.TryParse(BaseRequest.TranscodeReasons, out var reason); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index b038d8ff1a..5a639c10e3 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -167,7 +167,7 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.Has64BitOffsets: // TODO - return TranscodeReason.None; + return 0; case ProfileConditionValue.Height: return TranscodeReason.VideoResolutionNotSupported; @@ -177,7 +177,7 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.IsAvc: // TODO - return TranscodeReason.None; + return 0; case ProfileConditionValue.IsInterlaced: return TranscodeReason.InterlacedVideoNotSupported; @@ -187,15 +187,15 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.NumAudioStreams: // TODO - return TranscodeReason.None; + return 0; case ProfileConditionValue.NumVideoStreams: // TODO - return TranscodeReason.None; + return 0; case ProfileConditionValue.PacketLength: // TODO - return TranscodeReason.None; + return 0; case ProfileConditionValue.RefFrames: return TranscodeReason.RefFramesNotSupported; @@ -223,13 +223,13 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.VideoTimestamp: // TODO - return TranscodeReason.None; + return 0; case ProfileConditionValue.Width: return TranscodeReason.VideoResolutionNotSupported; default: - return TranscodeReason.None; + return 0; } } @@ -306,7 +306,7 @@ namespace MediaBrowser.Model.Dlna var audioFailureReasons = AggregateFailureConditions(item, profile, "AudioCodecProfile", audioFailureConditions); transcodeReasons |= audioFailureReasons; - if (audioFailureReasons == TranscodeReason.None) + if (audioFailureReasons == 0) { playlistItem.PlayMethod = directPlayMethod.Value; playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile); @@ -389,7 +389,7 @@ namespace MediaBrowser.Model.Dlna } var playMethods = new List(); - var transcodeReasons = TranscodeReason.None; + TranscodeReason transcodeReasons = 0; // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play @@ -399,7 +399,7 @@ namespace MediaBrowser.Model.Dlna { if (options.EnableDirectPlay) { - return (directPlayProfile, PlayMethod.DirectPlay, TranscodeReason.None); + return (directPlayProfile, PlayMethod.DirectPlay, 0); } } else @@ -434,7 +434,7 @@ namespace MediaBrowser.Model.Dlna var containerSupported = false; var audioSupported = false; var videoSupported = false; - var reasons = TranscodeReason.None; + TranscodeReason reasons = 0; foreach (var profile in directPlayProfiles) { @@ -617,8 +617,8 @@ namespace MediaBrowser.Model.Dlna // TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, options, PlayMethod.DirectPlay); var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream); - bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == TranscodeReason.None); - bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == TranscodeReason.None); + bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == 0); + bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == 0); var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult; _logger.LogDebug( @@ -1062,12 +1062,12 @@ namespace MediaBrowser.Model.Dlna { if (options.ForceDirectPlay) { - return (null, PlayMethod.DirectPlay, audioStream?.Index, TranscodeReason.None); + return (null, PlayMethod.DirectPlay, audioStream?.Index, 0); } if (options.ForceDirectStream) { - return (null, PlayMethod.DirectStream, audioStream?.Index, TranscodeReason.None); + return (null, PlayMethod.DirectStream, audioStream?.Index, 0); } DeviceProfile profile = options.Profile; @@ -1130,7 +1130,7 @@ namespace MediaBrowser.Model.Dlna // Check audiocandidates profile conditions var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked)); - TranscodeReason subtitleProfileReasons = TranscodeReason.None; + TranscodeReason subtitleProfileReasons = 0; if (subtitleStream != null) { var subtitleProfile = GetSubtitleProfile(mediaSource, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.DirectPlay, _transcoderSupport, container, null); @@ -1151,7 +1151,7 @@ namespace MediaBrowser.Model.Dlna foreach (var flag in rankings) { var reason = a & flag; - if (reason != TranscodeReason.None) + if (reason != 0) { a = reason; return index; @@ -1168,8 +1168,8 @@ namespace MediaBrowser.Model.Dlna .Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video) .Select((directPlayProfile, order) => { - var directPlayProfileReasons = TranscodeReason.None; - var audioCodecProfileReasons = TranscodeReason.None; + TranscodeReason directPlayProfileReasons = 0; + TranscodeReason audioCodecProfileReasons = 0; // Check container type if (!directPlayProfile.SupportsContainer(container)) @@ -1199,11 +1199,11 @@ namespace MediaBrowser.Model.Dlna var directStreamFailureReasons = failureReasons & (~DirectStreamReasons); PlayMethod? playMethod = null; - if (failureReasons == TranscodeReason.None && isEligibleForDirectPlay && mediaSource.SupportsDirectPlay) + if (failureReasons == 0 && isEligibleForDirectPlay && mediaSource.SupportsDirectPlay) { playMethod = PlayMethod.DirectPlay; } - else if (directStreamFailureReasons == TranscodeReason.None && isEligibleForDirectStream && mediaSource.SupportsDirectStream && directPlayProfile != null) + else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream && directPlayProfile != null) { playMethod = PlayMethod.DirectStream; } @@ -1225,7 +1225,7 @@ namespace MediaBrowser.Model.Dlna } var failureReasons = analyzedProfiles[false].OrderBy(a => a.Result.TranscodeReason).ThenBy(analysis => analysis.Order).FirstOrDefault().Result.TranscodeReason; - if (failureReasons == TranscodeReason.None) + if (failureReasons == 0) { failureReasons = TranscodeReason.DirectPlayError; } @@ -1249,7 +1249,7 @@ namespace MediaBrowser.Model.Dlna private TranscodeReason AggregateFailureConditions(MediaSourceInfo mediaSource, DeviceProfile profile, string type, IEnumerable conditions) { - return conditions.Aggregate(TranscodeReason.None, (reasons, i) => + return conditions.Aggregate(0, (reasons, i) => { LogConditionFailure(profile, type, i, mediaSource); var transcodeReasons = GetTranscodeReasonForFailedCondition(i); @@ -1283,7 +1283,7 @@ namespace MediaBrowser.Model.Dlna } else { - return TranscodeReason.None; + return 0; } } diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs index c99136384e..9da9f3323b 100644 --- a/MediaBrowser.Model/Session/TranscodeReason.cs +++ b/MediaBrowser.Model/Session/TranscodeReason.cs @@ -7,8 +7,6 @@ namespace MediaBrowser.Model.Session [Flags] public enum TranscodeReason { - None = 0, - // Primary ContainerNotSupported = 1 << 0, VideoCodecNotSupported = 1 << 1, diff --git a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs index cc9a2f12ff..c645ca9a63 100644 --- a/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs +++ b/tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs @@ -29,7 +29,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, "Remux")] // #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 @@ -40,7 +40,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, "Remux")] // #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 @@ -58,24 +58,24 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, "Remux")] // #6450 - [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 - [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [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.None, "Remux")] // #6450 - [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [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.None, "Remux")] // #6450 - [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 should be DirectPlay - [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [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.None, "Remux")] // #6450 - [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #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.None, "Remux")] // #6450 - [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 - [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [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 @@ -91,7 +91,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, "Remux")] // #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")] @@ -104,12 +104,12 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, "Remux")] - [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] - [InlineData("DirectMedia", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] - [InlineData("DirectMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] - [InlineData("DirectMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] - [InlineData("DirectMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] + [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)] @@ -131,7 +131,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") + 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); @@ -148,7 +148,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, "Remux")] // #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 @@ -158,7 +158,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, "Remux")] // #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 @@ -175,30 +175,30 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, "Remux")] // #6450 + [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.None, "Remux")] // #6450 + [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.None, "Remux")] // #6450 + [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.None, "Remux")] // #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.None, "Remux")] // #6450 - [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 - [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [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.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") + 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; @@ -218,12 +218,12 @@ namespace Jellyfin.MediaBrowser.Model.Tests [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.None, "Remux")] // #6450 - [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 + [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.None, "Remux")] // #6450 - [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 - public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "") + [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; @@ -320,7 +320,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests // Full transcode if (transcodeMode == "Transcode") { - if ((val.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == TranscodeReason.None) + if ((val.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == 0) { Assert.All( videoStreams, @@ -362,7 +362,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue); // audio codec not supported - if ((why & TranscodeReason.AudioCodecNotSupported) != TranscodeReason.None) + if ((why & TranscodeReason.AudioCodecNotSupported) != 0) { // audio stream specified if (options.AudioStreamIndex >= 0) 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 0000000000..c8652b3233 --- /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() + } + }; + + [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); + } +} From dbea7cac674b9dca0a4e9bd26cac9e3838007b65 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 5 Mar 2022 14:47:16 -0700 Subject: [PATCH 08/10] Use Enum.GetValues() --- .../Json/Converters/JsonFlagEnumConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs index 6e4c85c474..4fa91fa5e1 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs @@ -9,9 +9,9 @@ namespace Jellyfin.Extensions.Json.Converters; /// /// The type of enum. public class JsonFlagEnumConverter : JsonConverter - where T : Enum + where T : struct, Enum { - private static readonly T[] _enumValues = (T[])Enum.GetValues(typeof(T)); + private static readonly T[] _enumValues = Enum.GetValues(); /// public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) From da41cd365c1bcbb08d316f070dd42a387671eae0 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 6 Mar 2022 18:17:49 -0700 Subject: [PATCH 09/10] Suggestions from review --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 5a639c10e3..057df9c383 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -909,9 +909,8 @@ namespace MediaBrowser.Model.Dlna } // Make sure the video bitrate is lower than bitrate settings but at least 64k - long currentValue = playlistItem.VideoBitrate ?? availableBitrateForVideo; - var longBitrate = Math.Max(Math.Min(availableBitrateForVideo, currentValue), 64000); - playlistItem.VideoBitrate = longBitrate >= int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); + var currentValue = playlistItem.VideoBitrate ?? availableBitrateForVideo; + playlistItem.VideoBitrate = Math.Clamp(currentValue, availableBitrateForVideo, 64_000); } _logger.LogInformation( From 3ea4174d12cdb14bc7cbe693147d42bc45aaabb0 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 12 Mar 2022 07:17:59 -0700 Subject: [PATCH 10/10] Fix flipped Clamp args --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 057df9c383..e72cd632d1 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -910,7 +910,7 @@ namespace MediaBrowser.Model.Dlna // Make sure the video bitrate is lower than bitrate settings but at least 64k var currentValue = playlistItem.VideoBitrate ?? availableBitrateForVideo; - playlistItem.VideoBitrate = Math.Clamp(currentValue, availableBitrateForVideo, 64_000); + playlistItem.VideoBitrate = Math.Clamp(currentValue, 64_000, availableBitrateForVideo); } _logger.LogInformation(