diff options
Diffstat (limited to 'tests')
19 files changed, 2202 insertions, 45 deletions
diff --git a/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs index a74dab5f2..e95df1635 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs @@ -87,7 +87,7 @@ public class UserControllerTests Assert.Contains( Validate(userPolicy), v => v.MemberNames.Contains("PasswordResetProviderId") && - v.ErrorMessage != null && + v.ErrorMessage is not null && v.ErrorMessage.Contains("required", StringComparison.CurrentCultureIgnoreCase)); } @@ -105,7 +105,7 @@ public class UserControllerTests Assert.Contains(Validate(userPolicy), v => v.MemberNames.Contains("AuthenticationProviderId") && - v.ErrorMessage != null && + v.ErrorMessage is not null && v.ErrorMessage.Contains("required", StringComparison.CurrentCultureIgnoreCase)); } diff --git a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs index 07b53bf74..1f59908a8 100644 --- a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs +++ b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs @@ -181,8 +181,8 @@ namespace Jellyfin.Controller.Tests fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata); var secondResult = directoryService.GetFile(path); - Assert.Equal(cachedFileSystemMetadata, result); - Assert.Equal(cachedFileSystemMetadata, secondResult); + Assert.Equivalent(cachedFileSystemMetadata, result); + Assert.Equivalent(cachedFileSystemMetadata, secondResult); } [Fact] diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index df51d39cb..94710a095 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -289,6 +289,40 @@ namespace Jellyfin.MediaEncoding.Tests.Probing } [Fact] + public void GetMediaInfo_VideoWithSingleFrameMjpeg_Success() + { + var bytes = File.ReadAllBytes("Test Data/Probing/video_single_frame_mjpeg.json"); + + var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions); + MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_interlaced.mp4", MediaProtocol.File); + + Assert.Equal(3, res.MediaStreams.Count); + + Assert.NotNull(res.VideoStream); + Assert.Equal(res.MediaStreams[0], res.VideoStream); + Assert.Equal(0, res.VideoStream.Index); + Assert.Equal("h264", res.VideoStream.Codec); + Assert.Equal("High", res.VideoStream.Profile); + Assert.Equal(MediaStreamType.Video, res.VideoStream.Type); + Assert.Equal(1080, res.VideoStream.Height); + Assert.Equal(1920, res.VideoStream.Width); + Assert.False(res.VideoStream.IsInterlaced); + Assert.Equal("16:9", res.VideoStream.AspectRatio); + Assert.Equal("yuv420p", res.VideoStream.PixelFormat); + Assert.Equal(42d, res.VideoStream.Level); + Assert.Equal(1, res.VideoStream.RefFrames); + Assert.True(res.VideoStream.IsAVC); + Assert.Equal(50f, res.VideoStream.RealFrameRate); + Assert.Equal("1/1000", res.VideoStream.TimeBase); + Assert.Equal(8, res.VideoStream.BitDepth); + Assert.True(res.VideoStream.IsDefault); + + var mjpeg = res.MediaStreams[2]; + Assert.NotNull(mjpeg); + Assert.Equal("mjpeg", mjpeg.Codec); + } + + [Fact] public void GetMediaInfo_MusicVideo_Success() { var bytes = File.ReadAllBytes("Test Data/Probing/music_video_metadata.json"); diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_single_frame_mjpeg.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_single_frame_mjpeg.json new file mode 100644 index 000000000..261f044bb --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_single_frame_mjpeg.json @@ -0,0 +1,209 @@ +{ + "streams": [ + { + "index": 0, + "codec_name": "h264", + "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", + "profile": "High", + "codec_type": "video", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "width": 1920, + "height": 1080, + "coded_width": 1920, + "coded_height": 1080, + "closed_captions": 0, + "film_grain": 0, + "has_b_frames": 0, + "sample_aspect_ratio": "1:1", + "display_aspect_ratio": "16:9", + "pix_fmt": "yuv420p", + "level": 42, + "chroma_location": "left", + "field_order": "progressive", + "refs": 1, + "is_avc": "true", + "nal_length_size": "4", + "r_frame_rate": "50/1", + "avg_frame_rate": "50/1", + "time_base": "1/1000", + "start_pts": 0, + "start_time": "0.000000", + "bits_per_raw_sample": "8", + "extradata_size": 55, + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0, + "non_diegetic": 0, + "captions": 0, + "descriptions": 0, + "metadata": 0, + "dependent": 0, + "still_image": 0 + }, + "tags": { + "language": "deu", + "HANDLER_NAME": "VideoHandler", + "VENDOR_ID": "[0][0][0][0]", + "BPS": "3950584", + "DURATION": "00:00:10.000000000", + "NUMBER_OF_FRAMES": "500", + "NUMBER_OF_BYTES": "4938231", + "_STATISTICS_WRITING_APP": "mkvpropedit v90.0 ('Hanging On') 64-bit", + "_STATISTICS_WRITING_DATE_UTC": "2025-04-19 10:37:57", + "_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES" + } + }, + { + "index": 1, + "codec_name": "aac", + "codec_long_name": "AAC (Advanced Audio Coding)", + "profile": "LC", + "codec_type": "audio", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "sample_fmt": "fltp", + "sample_rate": "48000", + "channels": 2, + "channel_layout": "stereo", + "bits_per_sample": 0, + "initial_padding": 0, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/1000", + "start_pts": 0, + "start_time": "0.000000", + "extradata_size": 2, + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0, + "non_diegetic": 0, + "captions": 0, + "descriptions": 0, + "metadata": 0, + "dependent": 0, + "still_image": 0 + }, + "tags": { + "language": "deu", + "HANDLER_NAME": "SoundHandler", + "VENDOR_ID": "[0][0][0][0]", + "BPS": "255785", + "DURATION": "00:00:09.984000000", + "NUMBER_OF_FRAMES": "469", + "NUMBER_OF_BYTES": "319220", + "_STATISTICS_WRITING_APP": "mkvpropedit v90.0 ('Hanging On') 64-bit", + "_STATISTICS_WRITING_DATE_UTC": "2025-04-19 10:37:57", + "_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES" + } + }, + { + "index": 2, + "codec_name": "mjpeg", + "codec_long_name": "Motion JPEG", + "profile": "Baseline", + "codec_type": "video", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "width": 960, + "height": 540, + "coded_width": 960, + "coded_height": 540, + "closed_captions": 0, + "film_grain": 0, + "has_b_frames": 0, + "sample_aspect_ratio": "1:1", + "display_aspect_ratio": "16:9", + "pix_fmt": "yuvj420p", + "level": -99, + "color_range": "pc", + "color_space": "bt470bg", + "chroma_location": "center", + "refs": 1, + "r_frame_rate": "1000/1", + "avg_frame_rate": "30000/1", + "time_base": "1/1000", + "start_pts": 0, + "start_time": "0.000000", + "bits_per_raw_sample": "8", + "disposition": { + "default": 0, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0, + "non_diegetic": 0, + "captions": 0, + "descriptions": 0, + "metadata": 0, + "dependent": 0, + "still_image": 0 + }, + "tags": { + "BPS": "0", + "DURATION": "00:00:00.000011111", + "NUMBER_OF_FRAMES": "1", + "NUMBER_OF_BYTES": "155034", + "_STATISTICS_WRITING_APP": "mkvpropedit v90.0 ('Hanging On') 64-bit", + "_STATISTICS_WRITING_DATE_UTC": "2025-04-19 10:37:57", + "_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES" + } + } + ], + "chapters": [], + "format": { + "filename": "file:broken_mkv_covers - 01x03 - statistics added using mkvpropedit.mkv", + "nb_streams": 3, + "nb_programs": 0, + "nb_stream_groups": 0, + "format_name": "matroska,webm", + "format_long_name": "Matroska / WebM", + "start_time": "0.000000", + "duration": "10.005000", + "size": "5425928", + "bit_rate": "4338573", + "probe_score": 100, + "tags": { + "title": "Folge 1: Jackpot Immobilie · Wie wir klug vererben", + "EPISODE_SORT": "1", + "MAJOR_BRAND": "isom", + "MINOR_VERSION": "512", + "COMPATIBLE_BRANDS": "isomiso2avc1mp41", + "DATE": "20250318", + "SEASON_NUMBER": "1", + "COMMENT": "https://www.ardmediathek.de/video/Y3JpZDovL2JyLmRlL2Jyb2FkY2FzdC8zZWFmYTcxOC1mZDJmLTRmZTMtYWE4Ny03ZjdlNWViNTk1NDhfb25saW5lYnJvYWRjYXN0", + "DESCRIPTION": "Jeder kennt es: Enge Familienmitglieder sprechen plötzlich nicht mehr miteinander, der Streit ums Erbe entzweit die Familie. Was steht im Testament? Kann sich die Erbengemeinschaft einigen? Wie läuft das mit der Erbschaftssteuer und was sagt das Erbrecht?\n\nDas \"Lohnt sich das?\"-Team hat zwei Familien gefunden, die das Tabu rund ums Erben brechen. Sie erzählen, um wie viel Geld es geht, aber auch, was dieses Immobilienerbe mit ihrer Familie gemacht hat. Warum es einmal gelingt, Häuser in Millionenhöhe ohne jegliche Erbschaftssteuer zu vererben, während andere Geschwister sich über ihr Elternhaus komplett zerstritten haben. Erbfolge, Freibeträge, Nießbrauch - für alle Basics rund ums Erben ist Ralph Caspers zuständig, der kompetent erklärt. ", + "SYNOPSIS": "Jeder kennt es: Enge Familienmitglieder sprechen plötzlich nicht mehr miteinander, der Streit ums Erbe entzweit die Familie. Was steht im Testament? Kann sich die Erbengemeinschaft einigen? Wie läuft das mit der Erbschaftssteuer und was sagt das Erbrecht?\n\nDas \"Lohnt sich das?\"-Team hat zwei Familien gefunden, die das Tabu rund ums Erben brechen. Sie erzählen, um wie viel Geld es geht, aber auch, was dieses Immobilienerbe mit ihrer Familie gemacht hat. Warum es einmal gelingt, Häuser in Millionenhöhe ohne jegliche Erbschaftssteuer zu vererben, während andere Geschwister sich über ihr Elternhaus komplett zerstritten haben. Erbfolge, Freibeträge, Nießbrauch - für alle Basics rund ums Erben ist Ralph Caspers zuständig, der kompetent erklärt. ", + "SHOW": "Generation Wohnkrise. Lohnt sich das?", + "EPISODE_ID": "Folge 1: Jackpot Immobilie · Wie wir klug vererben", + "ENCODER": "Lavf61.7.100" + } + } +} diff --git a/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs b/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs new file mode 100644 index 000000000..e32baef55 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; + +namespace Jellyfin.Model.Tests.Dlna; + +public class LegacyStreamInfo : StreamInfo +{ + public LegacyStreamInfo(Guid itemId, DlnaProfileType mediaType) + { + ItemId = itemId; + MediaType = mediaType; + } + + /// <summary> + /// The 10.6 ToUrl code from StreamInfo.cs with which to compare new version. + /// </summary> + /// <param name="baseUrl">The base url to use.</param> + /// <param name="accessToken">The Access token.</param> + /// <returns>A url.</returns> + public string ToUrl_Original(string baseUrl, string? accessToken) + { + ArgumentException.ThrowIfNullOrEmpty(baseUrl); + + var list = new List<string>(); + foreach (NameValuePair pair in BuildParams(this, accessToken)) + { + if (string.IsNullOrEmpty(pair.Value)) + { + continue; + } + + // Try to keep the url clean by omitting defaults + if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase) + && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) + && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) + && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal); + + list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue)); + } + + string queryString = string.Join('&', list); + + return GetUrl(baseUrl, queryString); + } + + private string GetUrl(string baseUrl, string queryString) + { + ArgumentException.ThrowIfNullOrEmpty(baseUrl); + + string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; + + baseUrl = baseUrl.TrimEnd('/'); + + if (MediaType == DlnaProfileType.Audio) + { + if (SubProtocol == MediaStreamProtocol.hls) + { + return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString); + } + + return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); + } + + if (SubProtocol == MediaStreamProtocol.hls) + { + return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString); + } + + return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); + } + + private static List<NameValuePair> BuildParams(StreamInfo item, string? accessToken) + { + List<NameValuePair> list = []; + + string audioCodecs = item.AudioCodecs.Count == 0 ? + string.Empty : + string.Join(',', item.AudioCodecs); + + string videoCodecs = item.VideoCodecs.Count == 0 ? + string.Empty : + string.Join(',', item.VideoCodecs); + + list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty)); + list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty)); + list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty)); + list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + list.Add(new NameValuePair("VideoCodec", videoCodecs)); + list.Add(new NameValuePair("AudioCodec", audioCodecs)); + list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && (item.AlwaysBurnInSubtitleWhenTranscoding || item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + + list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + + long startPositionTicks = item.StartPositionTicks; + + if (item.SubProtocol == MediaStreamProtocol.hls) + { + list.Add(new NameValuePair("StartTimeTicks", string.Empty)); + list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty)); + + if (item.SegmentLength.HasValue) + { + list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture))); + } + + if (item.MinSegments.HasValue) + { + list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture))); + } + + list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture))); + } + else + { + list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture))); + } + + list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty)); + list.Add(new NameValuePair("ApiKey", accessToken ?? string.Empty)); + + string? liveStreamId = item.MediaSource?.LiveStreamId; + list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty)); + + if (!item.IsDirectStream) + { + if (item.RequireNonAnamorphic) + { + list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + + if (item.EnableSubtitlesInManifest) + { + list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + if (item.EnableMpegtsM2TsMode) + { + list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + if (item.EstimateContentLength) + { + list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto) + { + list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant())); + } + + if (item.CopyTimestamps) + { + list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + + list.Add(new NameValuePair("EnableAudioVbrEncoding", item.EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty)); + + string subtitleCodecs = item.SubtitleCodecs.Count == 0 ? string.Empty : string.Join(",", item.SubtitleCodecs); + list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty)); + list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty)); + + foreach (var pair in item.StreamOptions) + { + if (string.IsNullOrEmpty(pair.Value)) + { + continue; + } + + // strip spaces to avoid having to encode h264 profile names + list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal))); + } + + var transcodeReasonsValues = item.TranscodeReasons.GetUniqueFlags().ToArray(); + if (!item.IsDirectStream && transcodeReasonsValues.Length > 0) + { + list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString())); + } + + return list; + } +} diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs index bd2143f25..2c1080ffe 100644 --- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs +++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs @@ -39,6 +39,8 @@ namespace Jellyfin.Model.Tests [InlineData("Chrome", "mkv-dvhe.05-eac3-28000k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoRangeTypeNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode", "HLS.mp4")] [InlineData("Chrome", "mkv-dvhe.08-eac3-15200k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoRangeTypeNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode", "HLS.mp4")] [InlineData("Chrome", "mp4-dvhe.08-eac3-15200k", PlayMethod.Transcode, TranscodeReason.VideoRangeTypeNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode", "HLS.mp4")] + [InlineData("Chrome", "numstreams-32", PlayMethod.DirectPlay)] + [InlineData("Chrome", "numstreams-33", PlayMethod.DirectPlay)] // Firefox [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "DirectStream", "HLS.mp4")] // #6450 @@ -180,6 +182,12 @@ namespace Jellyfin.Model.Tests [InlineData("Tizen3-stereo", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen3-stereo", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen3-stereo", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] + [InlineData("Tizen3-stereo", "mkv-dvhe.08-eac3-15200k", PlayMethod.DirectPlay)] + [InlineData("Tizen3-stereo", "mp4-dvh1.05-eac3-15200k", PlayMethod.Transcode, TranscodeReason.VideoRangeTypeNotSupported | TranscodeReason.AudioChannelsNotSupported, "Transcode")] + [InlineData("Tizen3-stereo", "mp4-dvhe.08-eac3-15200k", PlayMethod.DirectPlay)] + [InlineData("Tizen3-stereo", "mkv-dvhe.05-eac3-28000k", PlayMethod.Transcode, TranscodeReason.VideoBitrateNotSupported | TranscodeReason.VideoRangeTypeNotSupported | TranscodeReason.AudioChannelsNotSupported, "Transcode")] + [InlineData("Tizen3-stereo", "numstreams-32", PlayMethod.DirectPlay)] + [InlineData("Tizen3-stereo", "numstreams-33", PlayMethod.Transcode, TranscodeReason.StreamCountExceedsLimit, "Remux")] // Tizen 4 4K 5.1 [InlineData("Tizen4-4K-5.1", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] @@ -191,6 +199,12 @@ namespace Jellyfin.Model.Tests [InlineData("Tizen4-4K-5.1", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen4-4K-5.1", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] + [InlineData("Tizen4-4K-5.1", "mkv-dvhe.08-eac3-15200k", PlayMethod.DirectPlay)] + [InlineData("Tizen4-4K-5.1", "mp4-dvh1.05-eac3-15200k", PlayMethod.Transcode, TranscodeReason.VideoRangeTypeNotSupported, "Transcode")] + [InlineData("Tizen4-4K-5.1", "mp4-dvhe.08-eac3-15200k", PlayMethod.DirectPlay)] + [InlineData("Tizen4-4K-5.1", "mkv-dvhe.05-eac3-28000k", PlayMethod.Transcode, TranscodeReason.VideoRangeTypeNotSupported, "Transcode")] + [InlineData("Tizen4-4K-5.1", "numstreams-32", PlayMethod.DirectPlay)] + [InlineData("Tizen4-4K-5.1", "numstreams-33", PlayMethod.Transcode, TranscodeReason.StreamCountExceedsLimit, "Remux")] // WebOS 23 [InlineData("WebOS-23", "mkv-dvhe.08-eac3-15200k", PlayMethod.Transcode, TranscodeReason.VideoRangeTypeNotSupported, "Remux")] [InlineData("WebOS-23", "mp4-dvh1.05-eac3-15200k", PlayMethod.DirectPlay)] @@ -588,7 +602,7 @@ namespace Jellyfin.Model.Tests private static (string Path, NameValueCollection Query, string Filename, string Extension) ParseUri(StreamInfo val) { - var href = val.ToUrl("media:", "ACCESSTOKEN").Split("?", 2); + var href = val.ToUrl("media:", "ACCESSTOKEN", null).Split("?", 2); var path = href[0]; var queryString = href.ElementAtOrDefault(1); diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamInfoTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamInfoTests.cs new file mode 100644 index 000000000..8dea46806 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Dlna/StreamInfoTests.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Text; +using MediaBrowser.Model.Dlna; +using Xunit; + +namespace Jellyfin.Model.Tests.Dlna; + +public class StreamInfoTests +{ + private const string BaseUrl = "/test/"; + private const int RandomSeed = 298347823; + + /// <summary> + /// Returns a random float. + /// </summary> + /// <param name="random">The <see cref="Random"/> instance.</param> + /// <returns>A random <see cref="float"/>.</returns> + private static float RandomFloat(Random random) + { + var buffer = new byte[4]; + random.NextBytes(buffer); + return BitConverter.ToSingle(buffer, 0); + } + + /// <summary> + /// Creates a random array. + /// </summary> + /// <param name="random">The <see cref="Random"/> instance.</param> + /// <param name="elementType">The element <see cref="Type"/> of the array.</param> + /// <returns>An <see cref="Array"/> of <see cref="Type"/>.</returns> + private static object? RandomArray(Random random, Type? elementType) + { + if (elementType is null) + { + return null; + } + + if (elementType == typeof(string)) + { + return RandomStringArray(random); + } + + if (elementType == typeof(int)) + { + return RandomIntArray(random); + } + + if (elementType.IsEnum) + { + var values = Enum.GetValues(elementType); + return RandomIntArray(random, 0, values.Length - 1); + } + + throw new ArgumentException("Unsupported array type " + elementType.ToString()); + } + + /// <summary> + /// Creates a random length string. + /// </summary> + /// <param name="random">The <see cref="Random"/> instance.</param> + /// <param name="minLength">The minimum length of the string.</param> + /// <param name="maxLength">The maximum length of the string.</param> + /// <returns>The string.</returns> + private static string RandomString(Random random, int minLength = 0, int maxLength = 256) + { + var len = random.Next(minLength, maxLength); + var sb = new StringBuilder(len); + + while (len > 0) + { + sb.Append((char)random.Next(65, 97)); + len--; + } + + return sb.ToString(); + } + + /// <summary> + /// Creates a random long. + /// </summary> + /// <param name="random">The <see cref="Random"/> instance.</param> + /// <param name="min">Min value.</param> + /// <param name="max">Max value.</param> + /// <returns>A random <see cref="long"/> between <paramref name="min"/> and <paramref name="max"/>.</returns> + private static long RandomLong(Random random, long min = -9223372036854775808, long max = 9223372036854775807) + { + long result = random.Next((int)(min >> 32), (int)(max >> 32)); + result <<= 32; + result |= (long)random.Next((int)(min >> 32) << 32, (int)(max >> 32) << 32); + return result; + } + + /// <summary> + /// Creates a random string array containing between <paramref name="minLength"/> and <paramref name="maxLength"/>. + /// </summary> + /// <param name="random">The <see cref="Random"/> instance.</param> + /// <param name="minLength">The minimum number of elements.</param> + /// <param name="maxLength">The maximum number of elements.</param> + /// <returns>A random <see cref="string[]"/> instance.</returns> + private static string[] RandomStringArray(Random random, int minLength = 0, int maxLength = 9) + { + var len = random.Next(minLength, maxLength); + var arr = new List<string>(len); + while (len > 0) + { + arr.Add(RandomString(random, 1, 30)); + len--; + } + + return arr.ToArray(); + } + + /// <summary> + /// Creates a random int array containing between <paramref name="minLength"/> and <paramref name="maxLength"/>. + /// </summary> + /// <param name="random">The <see cref="Random"/> instance.</param> + /// <param name="minLength">The minimum number of elements.</param> + /// <param name="maxLength">The maximum number of elements.</param> + /// <returns>A random <see cref="int[]"/> instance.</returns> + private static int[] RandomIntArray(Random random, int minLength = 0, int maxLength = 9) + { + var len = random.Next(minLength, maxLength); + var arr = new List<int>(len); + while (len > 0) + { + arr.Add(random.Next()); + len--; + } + + return arr.ToArray(); + } + + /// <summary> + /// Fills most properties with random data. + /// </summary> + /// <param name="destination">The instance to fill with data.</param> + private static void FillAllProperties<T>(T destination) + { + var random = new Random(RandomSeed); + var objectType = destination!.GetType(); + foreach (var property in objectType.GetProperties()) + { + if (!(property.CanRead && property.CanWrite)) + { + continue; + } + + var type = property.PropertyType; + // If nullable, then set it to null, 25% of the time. + if (Nullable.GetUnderlyingType(type) is not null) + { + if (random.Next(0, 4) == 0) + { + // Set it to null. + property.SetValue(destination, null); + continue; + } + } + + if (type == typeof(Guid)) + { + property.SetValue(destination, Guid.NewGuid()); + continue; + } + + if (type.IsEnum) + { + Array values = Enum.GetValues(property.PropertyType); + property.SetValue(destination, values.GetValue(random.Next(0, values.Length - 1))); + continue; + } + + if (type == typeof(long)) + { + property.SetValue(destination, RandomLong(random)); + continue; + } + + if (type == typeof(string)) + { + property.SetValue(destination, RandomString(random)); + continue; + } + + if (type == typeof(bool)) + { + property.SetValue(destination, random.Next(0, 1) == 1); + continue; + } + + if (type == typeof(float)) + { + property.SetValue(destination, RandomFloat(random)); + continue; + } + + if (type.IsArray) + { + property.SetValue(destination, RandomArray(random, type.GetElementType())); + continue; + } + } + } + + [InlineData(DlnaProfileType.Audio)] + [InlineData(DlnaProfileType.Video)] + [InlineData(DlnaProfileType.Photo)] + [Theory] + public void Test_Blank_Url_Method(DlnaProfileType type) + { + var streamInfo = new LegacyStreamInfo(Guid.Empty, type) + { + DeviceProfile = new DeviceProfile() + }; + + string legacyUrl = streamInfo.ToUrl_Original(BaseUrl, "123"); + + // New version will return and & after the ? due to optional parameters. + string newUrl = streamInfo.ToUrl(BaseUrl, "123", null).Replace("?&", "?", StringComparison.OrdinalIgnoreCase); + + Assert.Equal(legacyUrl, newUrl, ignoreCase: true); + } + + [Fact] + public void Fuzzy_Comparison() + { + var streamInfo = new LegacyStreamInfo(Guid.Empty, DlnaProfileType.Video) + { + DeviceProfile = new DeviceProfile() + }; + for (int i = 0; i < 100000; i++) + { + FillAllProperties(streamInfo); + string legacyUrl = streamInfo.ToUrl_Original(BaseUrl, "123"); + + // New version will return and & after the ? due to optional parameters. + string newUrl = streamInfo.ToUrl(BaseUrl, "123", null).Replace("?&", "?", StringComparison.OrdinalIgnoreCase); + + Assert.Equal(legacyUrl, newUrl, ignoreCase: true); + } + } +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json index 2e3e6e6de..9d43d2166 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json @@ -439,7 +439,14 @@ { "Condition": "EqualsAny", "Property": "VideoProfile", - "Value": "high|main|baseline|constrained baseline|high 10", + "Value": "high|main|baseline|constrained baseline", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoRangeType", + "Value": "SDR", "IsRequired": false, "$type": "ProfileCondition" }, @@ -479,6 +486,13 @@ "$type": "ProfileCondition" }, { + "Condition": "EqualsAny", + "Property": "VideoRangeType", + "Value": "SDR|DOVIWithSDR|HDR10|DOVIWithHDR10|HLG|DOVIWithHLG", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { "Condition": "LessThanEqual", "Property": "VideoLevel", "Value": "183", @@ -510,6 +524,21 @@ "$type": "CodecProfile" } ], + "ContainerProfiles": [ + { + "Type": "Video", + "Conditions": [ + { + "Condition": "LessThanEqual", + "Property": "NumStreams", + "Value": "32", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "$type": "ContainerProfile" + } + ], "ResponseProfiles": [ { "Container": "m4v", diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json index 156230471..3859ef994 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json @@ -439,7 +439,14 @@ { "Condition": "EqualsAny", "Property": "VideoProfile", - "Value": "high|main|baseline|constrained baseline|high 10", + "Value": "high|main|baseline|constrained baseline", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { + "Condition": "EqualsAny", + "Property": "VideoRangeType", + "Value": "SDR", "IsRequired": false, "$type": "ProfileCondition" }, @@ -472,6 +479,13 @@ "$type": "ProfileCondition" }, { + "Condition": "EqualsAny", + "Property": "VideoRangeType", + "Value": "SDR|DOVIWithSDR|HDR10|DOVIWithHDR10|HLG|DOVIWithHLG", + "IsRequired": false, + "$type": "ProfileCondition" + }, + { "Condition": "LessThanEqual", "Property": "VideoLevel", "Value": "183", @@ -483,6 +497,21 @@ "$type": "CodecProfile" } ], + "ContainerProfiles": [ + { + "Type": "Video", + "Conditions": [ + { + "Condition": "LessThanEqual", + "Property": "NumStreams", + "Value": "32", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "$type": "ContainerProfile" + } + ], "ResponseProfiles": [ { "Container": "m4v", diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-numstreams-32.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-numstreams-32.json new file mode 100644 index 000000000..6d01f8153 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-numstreams-32.json @@ -0,0 +1,565 @@ +{ + "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 + }, + { + "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 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 4, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 5, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 6, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 7, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 8, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 9, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 10, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 11, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 12, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 13, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 14, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 15, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 16, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 17, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 18, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 19, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 20, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 21, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 22, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 23, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 24, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 25, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 26, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 27, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 28, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 29, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 30, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 31, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-numstreams-33.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-numstreams-33.json new file mode 100644 index 000000000..ac24500fe --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-numstreams-33.json @@ -0,0 +1,582 @@ +{ + "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 + }, + { + "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 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 4, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 5, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 6, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 7, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 8, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 9, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 10, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 11, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 12, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 13, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 14, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 15, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 16, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 17, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 18, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 19, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 20, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 21, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 22, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 23, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 24, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 25, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 26, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 27, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 28, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 29, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 30, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 31, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 32, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 2 +} diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index 2c33ab492..51eb99f49 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -2,6 +2,7 @@ using Emby.Naming.Common; using Emby.Naming.Video; using MediaBrowser.Model.Entities; using Xunit; + using MediaType = Emby.Naming.Common.MediaType; namespace Jellyfin.Naming.Tests.Video @@ -20,6 +21,9 @@ namespace Jellyfin.Naming.Tests.Video { Test("trailer.mp4", ExtraType.Trailer); Test("300-trailer.mp4", ExtraType.Trailer); + Test("300.trailer.mp4", ExtraType.Trailer); + Test("300_trailer.mp4", ExtraType.Trailer); + Test("300 trailer.mp4", ExtraType.Trailer); Test("theme.mp3", ExtraType.ThemeSong); } @@ -43,6 +47,19 @@ namespace Jellyfin.Naming.Tests.Video Test("300-deletedscene.mp4", ExtraType.DeletedScene); Test("300-interview.mp4", ExtraType.Interview); Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes); + Test("300-featurette.mp4", ExtraType.Featurette); + Test("300-short.mp4", ExtraType.Short); + Test("300-extra.mp4", ExtraType.Unknown); + Test("300-other.mp4", ExtraType.Unknown); + } + + [Theory] + [InlineData(ExtraType.ThemeSong, "theme-music")] + public void TestDirectoriesAudioExtras(ExtraType type, string dirName) + { + Test(dirName + "/300.mp3", type); + Test("300/" + dirName + "/something.mp3", type); + Test("/data/something/Movies/300/" + dirName + "/whoknows.mp3", type); } [Theory] @@ -52,11 +69,14 @@ namespace Jellyfin.Naming.Tests.Video [InlineData(ExtraType.Scene, "scenes")] [InlineData(ExtraType.Sample, "samples")] [InlineData(ExtraType.Short, "shorts")] + [InlineData(ExtraType.Trailer, "trailers")] [InlineData(ExtraType.Featurette, "featurettes")] [InlineData(ExtraType.Clip, "clips")] [InlineData(ExtraType.ThemeVideo, "backdrops")] + [InlineData(ExtraType.Unknown, "extra")] [InlineData(ExtraType.Unknown, "extras")] - public void TestDirectories(ExtraType type, string dirName) + [InlineData(ExtraType.Unknown, "other")] + public void TestDirectoriesVideoExtras(ExtraType type, string dirName) { Test(dirName + "/300.mp4", type); Test("300/" + dirName + "/something.mkv", type); @@ -75,10 +95,44 @@ namespace Jellyfin.Naming.Tests.Video Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null); } + [Theory] + [InlineData(ExtraType.ThemeSong, "theme-music")] + public void TestTopLevelDirectoriesWithAudioExtraNames(ExtraType typicalType, string dirName) + { + string libraryRoot = "/data/something/" + dirName; + TestWithLibraryRoot(libraryRoot + "/300.mp3", libraryRoot, null); + TestWithLibraryRoot(libraryRoot + "/300/" + dirName + "/something.mp3", libraryRoot, typicalType); + } + + [Theory] + [InlineData(ExtraType.Trailer, "trailers")] + [InlineData(ExtraType.ThemeVideo, "backdrops")] + [InlineData(ExtraType.BehindTheScenes, "behind the scenes")] + [InlineData(ExtraType.DeletedScene, "deleted scenes")] + [InlineData(ExtraType.Interview, "interviews")] + [InlineData(ExtraType.Scene, "scenes")] + [InlineData(ExtraType.Sample, "samples")] + [InlineData(ExtraType.Short, "shorts")] + [InlineData(ExtraType.Featurette, "featurettes")] + [InlineData(ExtraType.Unknown, "extras")] + [InlineData(ExtraType.Unknown, "extra")] + [InlineData(ExtraType.Unknown, "other")] + [InlineData(ExtraType.Clip, "clips")] + public void TestTopLevelDirectoriesWithVideoExtraNames(ExtraType typicalType, string dirName) + { + string libraryRoot = "/data/something/" + dirName; + TestWithLibraryRoot(libraryRoot + "/300.mp4", libraryRoot, null); + TestWithLibraryRoot(libraryRoot + "/300/" + dirName + "/something.mkv", libraryRoot, typicalType); + } + [Fact] public void TestSample() { + Test("sample.mp4", ExtraType.Sample); Test("300-sample.mp4", ExtraType.Sample); + Test("300.sample.mp4", ExtraType.Sample); + Test("300_sample.mp4", ExtraType.Sample); + Test("300 sample.mp4", ExtraType.Sample); } private void Test(string input, ExtraType? expectedType) @@ -88,6 +142,12 @@ namespace Jellyfin.Naming.Tests.Video Assert.Equal(expectedType, extraType); } + private void TestWithLibraryRoot(string input, string libraryRoot, ExtraType? expectedType) + { + var extraType = ExtraRuleResolver.GetExtraInfo(input, _videoOptions, libraryRoot).ExtraType; + Assert.Equal(expectedType, extraType); + } + [Fact] public void TestExtraInfo_InvalidRuleType() { diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 4144300da..ef87e46a7 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -79,7 +79,10 @@ namespace Jellyfin.Networking.Tests [InlineData("[fe80::7add:12ff:febb:c67b%16]")] [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")] public static void TryParseValidIPStringsTrue(string address) - => Assert.True(NetworkUtils.TryParseToSubnet(address, out _)); + { + Assert.True(NetworkUtils.TryParseToSubnet(address, out _)); + Assert.True(NetworkUtils.TryParseToSubnet('!' + address, out _, true)); + } /// <summary> /// Checks invalid IP address formats. diff --git a/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs b/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs new file mode 100644 index 000000000..756a688ab --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs @@ -0,0 +1,41 @@ +using System.IO; +using MediaBrowser.Model.Lyrics; +using MediaBrowser.Providers.Lyric; +using Xunit; + +namespace Jellyfin.Providers.Tests.Lyrics; + +public static class LrcLyricParserTests +{ + [Fact] + public static void ParseElrcCues() + { + var parser = new LrcLyricParser(); + var fileContents = File.ReadAllText(Path.Combine("Test Data", "Lyrics", "Fleetwood Mac - Rumors.elrc")); + var parsed = parser.ParseLyrics(new LyricFile("Fleetwood Mac - Rumors.elrc", fileContents)); + + Assert.NotNull(parsed); + Assert.Equal(31, parsed.Lyrics.Count); + + var line1 = parsed.Lyrics[0]; + Assert.Equal("Every night that goes between", line1.Text); + Assert.NotNull(line1.Cues); + Assert.Equal(9, line1.Cues.Count); + Assert.Equal(68400000, line1.Cues[0].Start); + Assert.Equal(72000000, line1.Cues[0].End); + + var line5 = parsed.Lyrics[4]; + Assert.Equal("Every night you do not come", line5.Text); + Assert.NotNull(line5.Cues); + Assert.Equal(11, line5.Cues.Count); + Assert.Equal(377300000, line5.Cues[5].Start); + Assert.Equal(380000000, line5.Cues[5].End); + + var lastLine = parsed.Lyrics[^1]; + Assert.Equal("I have always been a storm", lastLine.Text); + Assert.NotNull(lastLine.Cues); + Assert.Equal(11, lastLine.Cues.Count); + Assert.Equal(2358000000, lastLine.Cues[^1].Start); + Assert.Null(lastLine.Cues[^1].End); + } +} diff --git a/tests/Jellyfin.Providers.Tests/Test Data/Lyrics/Fleetwood Mac - Rumors.elrc b/tests/Jellyfin.Providers.Tests/Test Data/Lyrics/Fleetwood Mac - Rumors.elrc new file mode 100644 index 000000000..1df264c90 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Test Data/Lyrics/Fleetwood Mac - Rumors.elrc @@ -0,0 +1,31 @@ +[00:06.84] <00:06.84> Every <00:07.20> <00:07.56> night <00:07.87> <00:08.19> that <00:08.46> <00:08.79> goes <00:09.19> <00:09.59> between +[00:14.69] <00:14.69> I <00:14.78> <00:14.87> feel <00:15.15> <00:15.44> a <00:15.54> <00:15.65> little <00:15.96> <00:16.28> less +[00:20.98] <00:20.98> As <00:21.10> <00:21.22> you <00:21.27> <00:21.31> slowly <00:21.79> <00:22.27> go <00:22.57> <00:22.87> away <00:23.74> <00:24.19> from <00:24.51> <00:24.82> me +[00:28.73] <00:28.73> This <00:28.91> <00:29.09> is <00:29.22> <00:29.36> only <00:29.75> <00:30.14> another <00:30.92> <00:31.25> test +[00:36.11] <00:36.11> Every <00:36.44> <00:36.77> night <00:37.14> <00:37.52> you <00:37.73> <00:38.00> do <00:38.24> <00:38.48> not <00:38.93> <00:39.41> come +[00:43.56] <00:43.56> Your <00:43.65> <00:43.74> softness <00:44.21> <00:44.69> fades <00:45.01> <00:45.33> away +[00:50.29] <00:50.29> Did <00:50.42> <00:50.56> I <00:50.70> <00:50.85> ever <00:51.41> <00:51.97> really <00:52.48> <00:52.99> care <00:53.56> <00:53.86> that <00:54.09> <00:54.34> much +[00:58.07] <00:58.07> Is <00:58.20> <00:58.34> there <00:58.48> <00:58.63> anything <00:59.44> <00:59.69> left <00:59.94> <01:00.20> to <01:00.30> <01:00.41> say? +[01:05.59] <01:05.59> Every <01:06.58> <01:07.78> hour <01:08.05> <01:08.32> of <01:08.92> <01:09.97> fear <01:10.39> <01:10.81> I <01:11.41> <01:11.47> spend +[01:13.84] <01:13.84> My <01:13.99> <01:14.14> body <01:14.57> <01:15.01> tries <01:15.32> <01:15.64> to <01:15.71> <01:15.79> cry +[01:18.60] <01:18.60> Living <01:19.41> <01:20.79> through <01:21.12> <01:21.45> each <01:21.90> <01:23.13> empty <01:23.83> <01:24.54> night +[01:27.51] <01:27.51> A <01:27.60> <01:27.69> deadly <01:28.12> <01:28.56> call <01:28.90> <01:29.25> inside +[01:34.29] <01:34.29> I <01:34.39> <01:34.50> haven't <01:35.37> <01:35.64> felt <01:35.92> <01:36.21> this <01:36.34> <01:36.48> way <01:36.79> <01:37.11> I <01:37.35> <01:37.62> feel +[01:42.11] <01:42.11> Since <01:42.27> <01:42.44> many <01:42.71> <01:42.98> a <01:43.08> <01:43.19> years <01:43.46> <01:43.85> ago +[01:48.92] <01:48.92> But <01:49.08> <01:49.25> in <01:49.36> <01:49.48> those <01:49.78> <01:50.09> years <01:50.50> <01:50.92> and <01:51.07> <01:51.23> the <01:51.30> <01:51.38> lifetime's <01:51.99> <01:52.61> past +[01:56.60] <01:56.60> I <01:56.69> <01:56.78> did <01:56.91> <01:57.05> not <01:57.50> <01:57.92> deal <01:58.22> <01:58.52> with <01:58.68> <01:58.85> the <01:58.91> <01:58.97> road +[02:03.13] <02:03.13> And <02:03.25> <02:03.37> I <02:03.45> <02:03.55> did <02:03.68> <02:03.81> not <02:04.20> <02:04.60> deal <02:04.94> <02:05.29> with <02:05.45> <02:05.62> you, <02:05.90> <02:06.19> I <02:06.50> <02:06.82> know +[02:10.95] <02:10.95> Though <02:11.11> <02:11.28> the <02:11.35> <02:11.43> love <02:11.79> <02:12.15> has <02:12.32> <02:12.39> always <02:13.06> <02:13.74> been +[02:17.70] <02:17.70> So <02:17.91> <02:18.12> I <02:18.16> <02:18.21> search <02:18.55> <02:18.90> to <02:19.03> <02:19.17> find <02:19.51> <02:19.86> an <02:20.14> <02:20.43> answer <02:21.24> <02:21.71> there +[02:25.72] <02:25.72> So <02:25.96> <02:26.20> I <02:26.39> <02:26.59> can <02:27.04> <02:27.19> truly <02:27.65> <02:28.12> win +[02:33.02] <02:33.02> Every <02:34.04> <02:35.29> hour <02:35.77> <02:35.84> of <02:36.38> <02:37.43> fear <02:37.92> <02:38.42> I <02:38.69> <02:38.96> spend +[02:41.35] <02:41.35> My <02:41.63> <02:41.91> body <02:42.35> <02:42.79> tries <02:43.09> <02:43.39> to <02:43.48> <02:43.57> cry +[02:46.32] <02:46.32> Living <02:47.04> <02:48.54> through <02:48.88> <02:49.23> each <02:49.56> <02:50.85> empty <02:51.55> <02:52.26> night +[02:55.06] <02:55.06> A <02:55.13> <02:55.21> deadly <02:55.64> <02:56.08> call <02:56.42> <02:56.77> inside +[03:01.50] <03:01.50> So <03:01.74> <03:01.98> I <03:02.04> <03:02.10> try <03:02.42> <03:02.76> to <03:02.81> <03:02.87> say <03:03.45> <03:04.20> goodbye, <03:04.53> <03:04.86> my <03:05.02> <03:05.19> friend +[03:09.09] <03:09.09> I'd <03:09.19> <03:09.30> like <03:09.42> <03:09.54> to <03:09.70> <03:09.87> leave <03:10.02> <03:10.17> you <03:10.29> <03:10.41> with <03:10.71> <03:10.89> something <03:11.32> <03:11.76> warm +[03:16.25] <03:16.25> But <03:16.34> <03:16.43> never <03:16.77> <03:17.12> have <03:17.25> <03:17.38> I <03:17.44> <03:17.51> been <03:17.73> <03:17.96> a <03:18.14> <03:18.32> blue <03:18.62> <03:18.92> calm <03:19.58> <03:19.76> sea +[03:24.18] <03:24.18> I <03:24.31> <03:24.45> have <03:24.57> <03:24.69> always <03:25.59> <03:28.02> been <03:28.19> <03:28.38> a <03:28.51> <03:28.65> storm +[03:33.69] <03:33.69> Always <03:35.43> <03:36.99> been <03:37.18> <03:37.38> a <03:37.50> <03:37.62> storm +[03:41.45] <03:41.45> Ooh, <03:41.99> <03:42.53> always <03:44.42> <03:45.86> been <03:46.05> <03:46.25> a <03:46.41> <03:46.58> storm +[03:50.34] <03:50.34> I <03:50.49> <03:50.64> have <03:51.07> <03:51.51> always <03:53.31> <03:54.78> been <03:54.96> <03:55.14> a <03:55.47> <03:55.80> storm diff --git a/tests/Jellyfin.Server.Implementations.Tests/Item/OrderMapperTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Item/OrderMapperTests.cs new file mode 100644 index 000000000..caf2b06b7 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Item/OrderMapperTests.cs @@ -0,0 +1,35 @@ +using System; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Server.Implementations.Item; +using MediaBrowser.Controller.Entities; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Item; + +public class OrderMapperTests +{ + [Fact] + public void ShouldReturnMappedOrderForSortingByPremierDate() + { + var orderFunc = OrderMapper.MapOrderByField(ItemSortBy.PremiereDate, new InternalItemsQuery()).Compile(); + + var expectedDate = new DateTime(1, 2, 3); + var expectedProductionYearDate = new DateTime(4, 1, 1); + + var entityWithOnlyProductionYear = new BaseItemEntity { Id = Guid.NewGuid(), Type = "Test", ProductionYear = expectedProductionYearDate.Year }; + var entityWithOnlyPremierDate = new BaseItemEntity { Id = Guid.NewGuid(), Type = "Test", PremiereDate = expectedDate }; + var entityWithBothPremierDateAndProductionYear = new BaseItemEntity { Id = Guid.NewGuid(), Type = "Test", PremiereDate = expectedDate, ProductionYear = expectedProductionYearDate.Year }; + var entityWithoutEitherPremierDateOrProductionYear = new BaseItemEntity { Id = Guid.NewGuid(), Type = "Test" }; + + var resultWithOnlyProductionYear = orderFunc(entityWithOnlyProductionYear); + var resultWithOnlyPremierDate = orderFunc(entityWithOnlyPremierDate); + var resultWithBothPremierDateAndProductionYear = orderFunc(entityWithBothPremierDateAndProductionYear); + var resultWithoutEitherPremierDateOrProductionYear = orderFunc(entityWithoutEitherPremierDateOrProductionYear); + + Assert.Equal(resultWithOnlyProductionYear, expectedProductionYearDate); + Assert.Equal(resultWithOnlyPremierDate, expectedDate); + Assert.Equal(resultWithBothPremierDateAndProductionYear, expectedDate); + Assert.Null(resultWithoutEitherPremierDateOrProductionYear); + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs index 5babc9117..a7a1e5e81 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using BitFaster.Caching; using Emby.Server.Implementations.Localization; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Configuration; @@ -51,6 +52,30 @@ namespace Jellyfin.Server.Implementations.Tests.Localization Assert.Contains("ger", germany.ThreeLetterISOLanguageNames); } + [Fact] + public async Task TryGetISO6392TFromB_Success() + { + var localizationManager = Setup(new ServerConfiguration + { + UICulture = "de-DE" + }); + await localizationManager.LoadAll(); + + string? isoT; + + // Translation ger -> deu + Assert.True(localizationManager.TryGetISO6392TFromB("ger", out isoT)); + Assert.Equal("deu", isoT); + + // chi -> zho + Assert.True(localizationManager.TryGetISO6392TFromB("chi", out isoT)); + Assert.Equal("zho", isoT); + + // eng is already ISO 639-2/T + Assert.False(localizationManager.TryGetISO6392TFromB("eng", out isoT)); + Assert.Null(isoT); + } + [Theory] [InlineData("de")] [InlineData("deu")] @@ -88,7 +113,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization var tvma = ratings.FirstOrDefault(x => x.Name.Equals("TV-MA", StringComparison.Ordinal)); Assert.NotNull(tvma); - Assert.Equal(17, tvma!.Value); + Assert.Equal(17, tvma!.RatingScore!.Score); } [Fact] @@ -105,47 +130,49 @@ namespace Jellyfin.Server.Implementations.Tests.Localization var fsk = ratings.FirstOrDefault(x => x.Name.Equals("FSK-12", StringComparison.Ordinal)); Assert.NotNull(fsk); - Assert.Equal(12, fsk!.Value); + Assert.Equal(12, fsk!.RatingScore!.Score); } [Theory] - [InlineData("CA-R", "CA", 18)] - [InlineData("FSK-16", "DE", 16)] - [InlineData("FSK-18", "DE", 18)] - [InlineData("FSK-18", "US", 18)] - [InlineData("TV-MA", "US", 17)] - [InlineData("XXX", "asdf", 1000)] - [InlineData("Germany: FSK-18", "DE", 18)] - [InlineData("Rated : R", "US", 17)] - [InlineData("Rated: R", "US", 17)] - [InlineData("Rated R", "US", 17)] - [InlineData(" PG-13 ", "US", 13)] - public async Task GetRatingLevel_GivenValidString_Success(string value, string countryCode, int expectedLevel) + [InlineData("CA-R", "CA", 18, 1)] + [InlineData("FSK-16", "DE", 16, null)] + [InlineData("FSK-18", "DE", 18, null)] + [InlineData("FSK-18", "US", 18, null)] + [InlineData("TV-MA", "US", 17, 1)] + [InlineData("XXX", "asdf", 1000, null)] + [InlineData("Germany: FSK-18", "DE", 18, null)] + [InlineData("Rated : R", "US", 17, 0)] + [InlineData("Rated: R", "US", 17, 0)] + [InlineData("Rated R", "US", 17, 0)] + [InlineData(" PG-13 ", "US", 13, 0)] + public async Task GetRatingLevel_GivenValidString_Success(string value, string countryCode, int? expectedScore, int? expectedSubScore) { var localizationManager = Setup(new ServerConfiguration() { MetadataCountryCode = countryCode }); await localizationManager.LoadAll(); - var level = localizationManager.GetRatingLevel(value); - Assert.NotNull(level); - Assert.Equal(expectedLevel, level!); + var score = localizationManager.GetRatingScore(value); + Assert.NotNull(score); + Assert.Equal(expectedScore, score.Score); + Assert.Equal(expectedSubScore, score.SubScore); } [Theory] - [InlineData("0", 0)] - [InlineData("1", 1)] - [InlineData("6", 6)] - [InlineData("12", 12)] - [InlineData("42", 42)] - [InlineData("9999", 9999)] - public async Task GetRatingLevel_GivenValidAge_Success(string value, int expectedLevel) + [InlineData("0", 0, null)] + [InlineData("1", 1, null)] + [InlineData("6", 6, null)] + [InlineData("12", 12, null)] + [InlineData("42", 42, null)] + [InlineData("9999", 9999, null)] + public async Task GetRatingLevel_GivenValidAge_Success(string value, int? expectedScore, int? expectedSubScore) { var localizationManager = Setup(new ServerConfiguration { MetadataCountryCode = "nl" }); await localizationManager.LoadAll(); - var level = localizationManager.GetRatingLevel(value); - Assert.NotNull(level); - Assert.Equal(expectedLevel, level); + var score = localizationManager.GetRatingScore(value); + Assert.NotNull(score); + Assert.Equal(expectedScore, score.Score); + Assert.Equal(expectedSubScore, score.SubScore); } [Fact] @@ -156,10 +183,10 @@ namespace Jellyfin.Server.Implementations.Tests.Localization UICulture = "de-DE" }); await localizationManager.LoadAll(); - Assert.Null(localizationManager.GetRatingLevel("NR")); - Assert.Null(localizationManager.GetRatingLevel("unrated")); - Assert.Null(localizationManager.GetRatingLevel("Not Rated")); - Assert.Null(localizationManager.GetRatingLevel("n/a")); + Assert.Null(localizationManager.GetRatingScore("NR")); + Assert.Null(localizationManager.GetRatingScore("unrated")); + Assert.Null(localizationManager.GetRatingScore("Not Rated")); + Assert.Null(localizationManager.GetRatingScore("n/a")); } [Theory] @@ -173,7 +200,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization }); await localizationManager.LoadAll(); - Assert.Null(localizationManager.GetRatingLevel(value)); + Assert.Null(localizationManager.GetRatingScore(value)); } [Theory] diff --git a/tests/Jellyfin.Server.Implementations.Tests/Playlists/PlaylistManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Playlists/PlaylistManagerTests.cs new file mode 100644 index 000000000..cc8ca720e --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Playlists/PlaylistManagerTests.cs @@ -0,0 +1,40 @@ +using Emby.Server.Implementations.Playlists; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Playlists; + +public class PlaylistManagerTests +{ + [Fact] + public void DetermineAdjustedIndexMoveToFirstPositionNoPriorInAllList() + { + var priorIndexAllChildren = 0; + var newIndex = 0; + + var adjustedIndex = PlaylistManager.DetermineAdjustedIndex(priorIndexAllChildren, newIndex); + + Assert.Equal(0, adjustedIndex); + } + + [Fact] + public void DetermineAdjustedIndexPriorInMiddleOfAllList() + { + var priorIndexAllChildren = 2; + var newIndex = 0; + + var adjustedIndex = PlaylistManager.DetermineAdjustedIndex(priorIndexAllChildren, newIndex); + + Assert.Equal(1, adjustedIndex); + } + + [Fact] + public void DetermineAdjustedIndexMoveMiddleOfPlaylist() + { + var priorIndexAllChildren = 2; + var newIndex = 1; + + var adjustedIndex = PlaylistManager.DetermineAdjustedIndex(priorIndexAllChildren, newIndex); + + Assert.Equal(3, adjustedIndex); + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs index 36861294b..c8ae2a88a 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs @@ -31,6 +31,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var config = new StartupConfigurationDto() { + ServerName = "NewServer", UICulture = "NewCulture", MetadataCountryCode = "be", PreferredMetadataLanguage = "nl" @@ -44,7 +45,8 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType); var newConfig = await getResponse.Content.ReadFromJsonAsync<StartupConfigurationDto>(_jsonOptions); - Assert.Equal(config.UICulture, newConfig!.UICulture); + Assert.Equal(config.ServerName, newConfig!.ServerName); + Assert.Equal(config.UICulture, newConfig.UICulture); Assert.Equal(config.MetadataCountryCode, newConfig.MetadataCountryCode); Assert.Equal(config.PreferredMetadataLanguage, newConfig.PreferredMetadataLanguage); } @@ -88,9 +90,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var newUser = await getResponse.Content.ReadFromJsonAsync<StartupUserDto>(_jsonOptions); Assert.NotNull(newUser); Assert.Equal(user.Name, newUser.Name); - Assert.NotNull(newUser.Password); - Assert.NotEmpty(newUser.Password); - Assert.NotEqual(user.Password, newUser.Password); + Assert.Null(newUser.Password); } [Fact] |
