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" }, @@ -216,58 +216,44 @@ "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, + { + "Container": "mp4", + "Type": "Video", + "AudioCodec": "aac,ac3,eac3,flac,alac", + "VideoCodec": "hevc,h264", + "Context": "Streaming", + "Protocol": "hls", + "MaxAudioChannels": "2", + "MinSegments": "2", + "BreakOnNonKeyFrames": true + }, { "Container": "ts", "Type": "Video", - "VideoCodec": "hevc,h264", "AudioCodec": "aac,mp3,ac3,eac3", - "Protocol": "hls", - "EstimateContentLength": false, - "EnableMpegtsM2TsMode": false, - "TranscodeSeekInfo": "Auto", - "CopyTimestamps": false, + "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", - "Protocol": "hls", - "EstimateContentLength": false, - "EnableMpegtsM2TsMode": false, - "TranscodeSeekInfo": "Auto", - "CopyTimestamps": false, + "AudioCodec": "aac,mp3", + "VideoCodec": "h264", "Context": "Streaming", - "EnableSubtitlesInManifest": false, - "MaxAudioChannels": "6", - "MinSegments": 0, - "SegmentLength": 0, - "BreakOnNonKeyFrames": false, + "Protocol": "hls", + "MaxAudioChannels": "2", + "MinSegments": "2", + "BreakOnNonKeyFrames": true, + "$type": "TranscodingProfile" + }, + { + "Container": "webm", + "Type": "Video", + "AudioCodec": "vorbis", + "VideoCodec": "vp9,vp8,vpx,av1", + "Context": "Streaming", + "Protocol": "http", + "MaxAudioChannels": "2", "$type": "TranscodingProfile" }, { 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 +}