Merge pull request #9374 from Shadowghost/fixup2

This commit is contained in:
Cody Robibero 2024-09-18 07:21:43 -06:00 committed by GitHub
commit 8c8972f0b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1876 additions and 1291 deletions

View File

@ -135,8 +135,8 @@ namespace Jellyfin.Server.Implementations.Devices
{ {
IEnumerable<Device> devices = _devices.Values IEnumerable<Device> devices = _devices.Values
.Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value)) .Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value))
.Where(device => query.DeviceId == null || device.DeviceId == query.DeviceId) .Where(device => query.DeviceId is null || device.DeviceId == query.DeviceId)
.Where(device => query.AccessToken == null || device.AccessToken == query.AccessToken) .Where(device => query.AccessToken is null || device.AccessToken == query.AccessToken)
.OrderBy(d => d.Id) .OrderBy(d => d.Id)
.ToList(); .ToList();
var count = devices.Count(); var count = devices.Count();

View File

@ -1,74 +1,94 @@
#nullable disable
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization; using System.Xml.Serialization;
using Jellyfin.Extensions; using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna;
{
/// <summary>
/// Defines the <see cref="CodecProfile"/>.
/// </summary>
public class CodecProfile public class CodecProfile
{ {
/// <summary>
/// Initializes a new instance of the <see cref="CodecProfile"/> class.
/// </summary>
public CodecProfile() public CodecProfile()
{ {
Conditions = Array.Empty<ProfileCondition>(); Conditions = [];
ApplyConditions = Array.Empty<ProfileCondition>(); ApplyConditions = [];
} }
/// <summary>
/// Gets or sets the <see cref="CodecType"/> which this container must meet.
/// </summary>
[XmlAttribute("type")] [XmlAttribute("type")]
public CodecType Type { get; set; } public CodecType Type { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="ProfileCondition"/> which this profile must meet.
/// </summary>
public ProfileCondition[] Conditions { get; set; } public ProfileCondition[] Conditions { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="ProfileCondition"/> to apply if this profile is met.
/// </summary>
public ProfileCondition[] ApplyConditions { get; set; } public ProfileCondition[] ApplyConditions { get; set; }
/// <summary>
/// Gets or sets the codec(s) that this profile applies to.
/// </summary>
[XmlAttribute("codec")] [XmlAttribute("codec")]
public string Codec { get; set; } public string? Codec { get; set; }
/// <summary>
/// Gets or sets the container(s) which this profile will be applied to.
/// </summary>
[XmlAttribute("container")] [XmlAttribute("container")]
public string Container { get; set; } public string? Container { get; set; }
/// <summary>
/// Gets or sets the sub-container(s) which this profile will be applied to.
/// </summary>
[XmlAttribute("subcontainer")] [XmlAttribute("subcontainer")]
public string SubContainer { get; set; } public string? SubContainer { get; set; }
public string[] GetCodecs() /// <summary>
{ /// Checks to see whether the codecs and containers contain the given parameters.
return ContainerProfile.SplitValue(Codec); /// </summary>
} /// <param name="codecs">The codecs to match.</param>
/// <param name="container">The container to match.</param>
private bool ContainsContainer(string container, bool useSubContainer = false) /// <param name="useSubContainer">Consider sub-containers.</param>
/// <returns>True if both conditions are met.</returns>
public bool ContainsAnyCodec(IReadOnlyList<string> codecs, string? container, bool useSubContainer = false)
{ {
var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container; var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
return ContainerProfile.ContainsContainer(containerToCheck, container); return ContainerHelper.ContainsContainer(containerToCheck, container) && codecs.Any(c => ContainerHelper.ContainsContainer(Codec, false, c));
} }
public bool ContainsAnyCodec(string codec, string container, bool useSubContainer = false) /// <summary>
/// Checks to see whether the codecs and containers contain the given parameters.
/// </summary>
/// <param name="codec">The codec to match.</param>
/// <param name="container">The container to match.</param>
/// <param name="useSubContainer">Consider sub-containers.</param>
/// <returns>True if both conditions are met.</returns>
public bool ContainsAnyCodec(string? codec, string? container, bool useSubContainer = false)
{ {
return ContainsAnyCodec(ContainerProfile.SplitValue(codec), container, useSubContainer); return ContainsAnyCodec(codec.AsSpan(), container, useSubContainer);
} }
public bool ContainsAnyCodec(string[] codec, string container, bool useSubContainer = false) /// <summary>
/// Checks to see whether the codecs and containers contain the given parameters.
/// </summary>
/// <param name="codec">The codec to match.</param>
/// <param name="container">The container to match.</param>
/// <param name="useSubContainer">Consider sub-containers.</param>
/// <returns>True if both conditions are met.</returns>
public bool ContainsAnyCodec(ReadOnlySpan<char> codec, string? container, bool useSubContainer = false)
{ {
if (!ContainsContainer(container, useSubContainer)) var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
{ return ContainerHelper.ContainsContainer(containerToCheck, container) && ContainerHelper.ContainsContainer(Codec, false, codec);
return false;
}
var codecs = GetCodecs();
if (codecs.Length == 0)
{
return true;
}
foreach (var val in codec)
{
if (codecs.Contains(val, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
} }
} }

View File

@ -1,74 +1,49 @@
#pragma warning disable CS1591 #pragma warning disable CA1819 // Properties should not return arrays
using System; using System;
using System.Collections.Generic;
using System.Xml.Serialization; using System.Xml.Serialization;
using Jellyfin.Extensions; using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna;
{
/// <summary>
/// Defines the <see cref="ContainerProfile"/>.
/// </summary>
public class ContainerProfile public class ContainerProfile
{ {
/// <summary>
/// Gets or sets the <see cref="DlnaProfileType"/> which this container must meet.
/// </summary>
[XmlAttribute("type")] [XmlAttribute("type")]
public DlnaProfileType Type { get; set; } public DlnaProfileType Type { get; set; }
public ProfileCondition[] Conditions { get; set; } = Array.Empty<ProfileCondition>(); /// <summary>
/// Gets or sets the list of <see cref="ProfileCondition"/> which this container will be applied to.
/// </summary>
public ProfileCondition[] Conditions { get; set; } = [];
/// <summary>
/// Gets or sets the container(s) which this container must meet.
/// </summary>
[XmlAttribute("container")] [XmlAttribute("container")]
public string Container { get; set; } = string.Empty; public string? Container { get; set; }
public static string[] SplitValue(string? value) /// <summary>
/// Gets or sets the sub container(s) which this container must meet.
/// </summary>
[XmlAttribute("subcontainer")]
public string? SubContainer { get; set; }
/// <summary>
/// Returns true if an item in <paramref name="container"/> appears in the <see cref="Container"/> property.
/// </summary>
/// <param name="container">The item to match.</param>
/// <param name="useSubContainer">Consider subcontainers.</param>
/// <returns>The result of the operation.</returns>
public bool ContainsContainer(ReadOnlySpan<char> container, bool useSubContainer = false)
{ {
if (string.IsNullOrEmpty(value)) var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
{ return ContainerHelper.ContainsContainer(containerToCheck, container);
return Array.Empty<string>();
}
return value.Split(',', StringSplitOptions.RemoveEmptyEntries);
}
public bool ContainsContainer(string? container)
{
var containers = SplitValue(Container);
return ContainsContainer(containers, container);
}
public static bool ContainsContainer(string? profileContainers, string? inputContainer)
{
var isNegativeList = false;
if (profileContainers is not null && profileContainers.StartsWith('-'))
{
isNegativeList = true;
profileContainers = profileContainers.Substring(1);
}
return ContainsContainer(SplitValue(profileContainers), isNegativeList, inputContainer);
}
public static bool ContainsContainer(string[]? profileContainers, string? inputContainer)
{
return ContainsContainer(profileContainers, false, inputContainer);
}
public static bool ContainsContainer(string[]? profileContainers, bool isNegativeList, string? inputContainer)
{
if (profileContainers is null || profileContainers.Length == 0)
{
// Empty profiles always support all containers/codecs
return true;
}
var allInputContainers = SplitValue(inputContainer);
foreach (var container in allInputContainers)
{
if (profileContainers.Contains(container, StringComparison.OrdinalIgnoreCase))
{
return !isNegativeList;
}
}
return isNegativeList;
}
} }
} }

View File

@ -1,10 +1,9 @@
#pragma warning disable CA1819 // Properties should not return arrays #pragma warning disable CA1819 // Properties should not return arrays
using System; using System;
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna;
{
/// <summary> /// <summary>
/// A <see cref="DeviceProfile" /> represents a set of metadata which determines which content a certain device is able to play. /// A <see cref="DeviceProfile" /> represents a set of metadata which determines which content a certain device is able to play.
/// <br/> /// <br/>
@ -16,15 +15,14 @@ namespace MediaBrowser.Model.Dlna
public class DeviceProfile public class DeviceProfile
{ {
/// <summary> /// <summary>
/// Gets or sets the name of this device profile. /// Gets or sets the name of this device profile. User profiles must have a unique name.
/// </summary> /// </summary>
public string? Name { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Id. /// Gets or sets the unique internal identifier.
/// </summary> /// </summary>
[XmlIgnore] public Guid Id { get; set; }
public string? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum allowed bitrate for all streamed content. /// Gets or sets the maximum allowed bitrate for all streamed content.
@ -49,26 +47,25 @@ namespace MediaBrowser.Model.Dlna
/// <summary> /// <summary>
/// Gets or sets the direct play profiles. /// Gets or sets the direct play profiles.
/// </summary> /// </summary>
public DirectPlayProfile[] DirectPlayProfiles { get; set; } = Array.Empty<DirectPlayProfile>(); public DirectPlayProfile[] DirectPlayProfiles { get; set; } = [];
/// <summary> /// <summary>
/// Gets or sets the transcoding profiles. /// Gets or sets the transcoding profiles.
/// </summary> /// </summary>
public TranscodingProfile[] TranscodingProfiles { get; set; } = Array.Empty<TranscodingProfile>(); public TranscodingProfile[] TranscodingProfiles { get; set; } = [];
/// <summary> /// <summary>
/// Gets or sets the container profiles. /// Gets or sets the container profiles. Failing to meet these optional conditions causes transcoding to occur.
/// </summary> /// </summary>
public ContainerProfile[] ContainerProfiles { get; set; } = Array.Empty<ContainerProfile>(); public ContainerProfile[] ContainerProfiles { get; set; } = [];
/// <summary> /// <summary>
/// Gets or sets the codec profiles. /// Gets or sets the codec profiles.
/// </summary> /// </summary>
public CodecProfile[] CodecProfiles { get; set; } = Array.Empty<CodecProfile>(); public CodecProfile[] CodecProfiles { get; set; } = [];
/// <summary> /// <summary>
/// Gets or sets the subtitle profiles. /// Gets or sets the subtitle profiles.
/// </summary> /// </summary>
public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty<SubtitleProfile>(); public SubtitleProfile[] SubtitleProfiles { get; set; } = [];
}
} }

View File

@ -1,36 +1,65 @@
#pragma warning disable CS1591
using System.Xml.Serialization; using System.Xml.Serialization;
using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna;
{
/// <summary>
/// Defines the <see cref="DirectPlayProfile"/>.
/// </summary>
public class DirectPlayProfile public class DirectPlayProfile
{ {
/// <summary>
/// Gets or sets the container.
/// </summary>
[XmlAttribute("container")] [XmlAttribute("container")]
public string? Container { get; set; } public string Container { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the audio codec.
/// </summary>
[XmlAttribute("audioCodec")] [XmlAttribute("audioCodec")]
public string? AudioCodec { get; set; } public string? AudioCodec { get; set; }
/// <summary>
/// Gets or sets the video codec.
/// </summary>
[XmlAttribute("videoCodec")] [XmlAttribute("videoCodec")]
public string? VideoCodec { get; set; } public string? VideoCodec { get; set; }
/// <summary>
/// Gets or sets the Dlna profile type.
/// </summary>
[XmlAttribute("type")] [XmlAttribute("type")]
public DlnaProfileType Type { get; set; } public DlnaProfileType Type { get; set; }
/// <summary>
/// Returns whether the <see cref="Container"/> supports the <paramref name="container"/>.
/// </summary>
/// <param name="container">The container to match against.</param>
/// <returns>True if supported.</returns>
public bool SupportsContainer(string? container) public bool SupportsContainer(string? container)
{ {
return ContainerProfile.ContainsContainer(Container, container); return ContainerHelper.ContainsContainer(Container, container);
} }
/// <summary>
/// Returns whether the <see cref="VideoCodec"/> supports the <paramref name="codec"/>.
/// </summary>
/// <param name="codec">The codec to match against.</param>
/// <returns>True if supported.</returns>
public bool SupportsVideoCodec(string? codec) public bool SupportsVideoCodec(string? codec)
{ {
return Type == DlnaProfileType.Video && ContainerProfile.ContainsContainer(VideoCodec, codec); return Type == DlnaProfileType.Video && ContainerHelper.ContainsContainer(VideoCodec, codec);
} }
/// <summary>
/// Returns whether the <see cref="AudioCodec"/> supports the <paramref name="codec"/>.
/// </summary>
/// <param name="codec">The codec to match against.</param>
/// <returns>True if supported.</returns>
public bool SupportsAudioCodec(string? codec) public bool SupportsAudioCodec(string? codec)
{ {
return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec); // Video profiles can have audio codec restrictions too, therefore incude Video as valid type.
} return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerHelper.ContainsContainer(AudioCodec, codec);
} }
} }

View File

@ -6,6 +6,7 @@ using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -27,9 +28,9 @@ namespace MediaBrowser.Model.Dlna
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport; private readonly ITranscoderSupport _transcoderSupport;
private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc", "vp9", "av1" }; private static readonly string[] _supportedHlsVideoCodecs = ["h264", "hevc", "vp9", "av1"];
private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" }; private static readonly string[] _supportedHlsAudioCodecsTs = ["aac", "ac3", "eac3", "mp3"];
private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" }; private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd"];
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StreamBuilder"/> class. /// Initializes a new instance of the <see cref="StreamBuilder"/> class.
@ -51,7 +52,7 @@ namespace MediaBrowser.Model.Dlna
{ {
ValidateMediaOptions(options, false); ValidateMediaOptions(options, false);
var streams = new List<StreamInfo>(); List<StreamInfo> streams = [];
foreach (var mediaSource in options.MediaSources) foreach (var mediaSource in options.MediaSources)
{ {
if (!(string.IsNullOrEmpty(options.MediaSourceId) if (!(string.IsNullOrEmpty(options.MediaSourceId)
@ -64,7 +65,7 @@ namespace MediaBrowser.Model.Dlna
if (streamInfo is not null) if (streamInfo is not null)
{ {
streamInfo.DeviceId = options.DeviceId; streamInfo.DeviceId = options.DeviceId;
streamInfo.DeviceProfileId = options.Profile.Id; streamInfo.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture);
streams.Add(streamInfo); streams.Add(streamInfo);
} }
} }
@ -129,7 +130,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlayMethod is PlayMethod.DirectStream) if (directPlayMethod is PlayMethod.DirectStream)
{ {
var remuxContainer = item.TranscodingContainer ?? "ts"; var remuxContainer = item.TranscodingContainer ?? "ts";
var supportedHlsContainers = new[] { "ts", "mp4" }; string[] supportedHlsContainers = ["ts", "mp4"];
// If the container specified for the profile is an HLS supported container, use that container instead, overriding the preference // If the container specified for the profile is an HLS supported container, use that container instead, overriding the preference
// The client should be responsible to ensure this container is compatible // The client should be responsible to ensure this container is compatible
remuxContainer = Array.Exists(supportedHlsContainers, element => string.Equals(element, directPlayInfo.Profile?.Container, StringComparison.OrdinalIgnoreCase)) ? directPlayInfo.Profile?.Container : remuxContainer; remuxContainer = Array.Exists(supportedHlsContainers, element => string.Equals(element, directPlayInfo.Profile?.Container, StringComparison.OrdinalIgnoreCase)) ? directPlayInfo.Profile?.Container : remuxContainer;
@ -226,7 +227,7 @@ namespace MediaBrowser.Model.Dlna
? options.MediaSources ? options.MediaSources
: options.MediaSources.Where(x => string.Equals(x.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)); : options.MediaSources.Where(x => string.Equals(x.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase));
var streams = new List<StreamInfo>(); List<StreamInfo> streams = [];
foreach (var mediaSourceInfo in mediaSources) foreach (var mediaSourceInfo in mediaSources)
{ {
var streamInfo = BuildVideoItem(mediaSourceInfo, options); var streamInfo = BuildVideoItem(mediaSourceInfo, options);
@ -239,7 +240,7 @@ namespace MediaBrowser.Model.Dlna
foreach (var stream in streams) foreach (var stream in streams)
{ {
stream.DeviceId = options.DeviceId; stream.DeviceId = options.DeviceId;
stream.DeviceProfileId = options.Profile.Id; stream.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture);
} }
return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0); return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0);
@ -388,32 +389,32 @@ namespace MediaBrowser.Model.Dlna
/// <param name="type">The <see cref="DlnaProfileType"/>.</param> /// <param name="type">The <see cref="DlnaProfileType"/>.</param>
/// <param name="playProfile">The <see cref="DirectPlayProfile"/> object to get the video stream from.</param> /// <param name="playProfile">The <see cref="DirectPlayProfile"/> object to get the video stream from.</param>
/// <returns>The normalized input container.</returns> /// <returns>The normalized input container.</returns>
public static string? NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null) public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null)
{ {
if (string.IsNullOrEmpty(inputContainer)) if (profile is null || !inputContainer.Contains(',', StringComparison.OrdinalIgnoreCase))
{ {
return null; return inputContainer;
} }
var formats = ContainerProfile.SplitValue(inputContainer); var formats = ContainerHelper.Split(inputContainer);
var playProfiles = playProfile is null ? profile.DirectPlayProfiles : [playProfile];
if (profile is not null)
{
var playProfiles = playProfile is null ? profile.DirectPlayProfiles : new[] { playProfile };
foreach (var format in formats) foreach (var format in formats)
{ {
foreach (var directPlayProfile in playProfiles) foreach (var directPlayProfile in playProfiles)
{ {
if (directPlayProfile.Type == type if (directPlayProfile.Type != type)
&& directPlayProfile.SupportsContainer(format)) {
continue;
}
if (directPlayProfile.SupportsContainer(format))
{ {
return format; return format;
} }
} }
} }
}
return formats[0]; return inputContainer;
} }
private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, MediaOptions options) private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, MediaOptions options)
@ -533,7 +534,6 @@ namespace MediaBrowser.Model.Dlna
private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles) private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
{ {
int highestScore = -1; int highestScore = -1;
foreach (var stream in item.MediaStreams) foreach (var stream in item.MediaStreams)
{ {
if (stream.Type == MediaStreamType.Subtitle if (stream.Type == MediaStreamType.Subtitle
@ -544,7 +544,7 @@ namespace MediaBrowser.Model.Dlna
} }
} }
var topStreams = new List<MediaStream>(); List<MediaStream> topStreams = [];
foreach (var stream in item.MediaStreams) foreach (var stream in item.MediaStreams)
{ {
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore) if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore)
@ -623,8 +623,8 @@ namespace MediaBrowser.Model.Dlna
playlistItem.Container = container; playlistItem.Container = container;
playlistItem.SubProtocol = protocol; playlistItem.SubProtocol = protocol;
playlistItem.VideoCodecs = new[] { item.VideoStream.Codec }; playlistItem.VideoCodecs = [item.VideoStream.Codec];
playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec); playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec);
} }
private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options) private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options)
@ -651,7 +651,7 @@ namespace MediaBrowser.Model.Dlna
} }
// Collect candidate audio streams // Collect candidate audio streams
ICollection<MediaStream> candidateAudioStreams = audioStream is null ? Array.Empty<MediaStream>() : new[] { audioStream }; ICollection<MediaStream> candidateAudioStreams = audioStream is null ? [] : [audioStream];
if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0) if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0)
{ {
if (audioStream?.IsDefault == true) if (audioStream?.IsDefault == true)
@ -702,7 +702,8 @@ namespace MediaBrowser.Model.Dlna
directPlayProfile = directPlayInfo.Profile; directPlayProfile = directPlayInfo.Profile;
playlistItem.PlayMethod = directPlay.Value; playlistItem.PlayMethod = directPlay.Value;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile); playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile);
playlistItem.VideoCodecs = new[] { videoStream.Codec }; var videoCodec = videoStream?.Codec;
playlistItem.VideoCodecs = videoCodec is null ? [] : [videoCodec];
if (directPlay == PlayMethod.DirectPlay) if (directPlay == PlayMethod.DirectPlay)
{ {
@ -713,7 +714,7 @@ namespace MediaBrowser.Model.Dlna
{ {
playlistItem.AudioStreamIndex = audioStreamIndex; playlistItem.AudioStreamIndex = audioStreamIndex;
var audioCodec = item.GetMediaStream(MediaStreamType.Audio, audioStreamIndex.Value)?.Codec; var audioCodec = item.GetMediaStream(MediaStreamType.Audio, audioStreamIndex.Value)?.Codec;
playlistItem.AudioCodecs = audioCodec is null ? Array.Empty<string>() : new[] { audioCodec }; playlistItem.AudioCodecs = audioCodec is null ? [] : [audioCodec];
} }
} }
else if (directPlay == PlayMethod.DirectStream) else if (directPlay == PlayMethod.DirectStream)
@ -721,7 +722,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioStreamIndex = audioStream?.Index; playlistItem.AudioStreamIndex = audioStream?.Index;
if (audioStream is not null) if (audioStream is not null)
{ {
playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec); playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec);
} }
SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile); SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile);
@ -753,7 +754,7 @@ namespace MediaBrowser.Model.Dlna
{ {
// Can't direct play, find the transcoding profile // Can't direct play, find the transcoding profile
// If we do this for direct-stream we will overwrite the info // If we do this for direct-stream we will overwrite the info
var (transcodingProfile, playMethod) = GetVideoTranscodeProfile(item, options, videoStream, audioStream, candidateAudioStreams, subtitleStream, playlistItem); var (transcodingProfile, playMethod) = GetVideoTranscodeProfile(item, options, videoStream, audioStream, playlistItem);
if (transcodingProfile is not null && playMethod.HasValue) if (transcodingProfile is not null && playMethod.HasValue)
{ {
@ -781,7 +782,7 @@ namespace MediaBrowser.Model.Dlna
} }
playlistItem.SubtitleFormat = subtitleProfile.Format; playlistItem.SubtitleFormat = subtitleProfile.Format;
playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format }; playlistItem.SubtitleCodecs = [subtitleProfile.Format];
} }
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0) if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
@ -810,8 +811,6 @@ namespace MediaBrowser.Model.Dlna
MediaOptions options, MediaOptions options,
MediaStream? videoStream, MediaStream? videoStream,
MediaStream? audioStream, MediaStream? audioStream,
IEnumerable<MediaStream> candidateAudioStreams,
MediaStream? subtitleStream,
StreamInfo playlistItem) StreamInfo playlistItem)
{ {
if (!(item.SupportsTranscoding || item.SupportsDirectStream)) if (!(item.SupportsTranscoding || item.SupportsDirectStream))
@ -849,9 +848,7 @@ namespace MediaBrowser.Model.Dlna
if (options.AllowVideoStreamCopy) if (options.AllowVideoStreamCopy)
{ {
var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec); if (ContainerHelper.ContainsContainer(transcodingProfile.VideoCodec, videoCodec))
if (ContainerProfile.ContainsContainer(videoCodecs, videoCodec))
{ {
var appliedVideoConditions = options.Profile.CodecProfiles var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video && .Where(i => i.Type == CodecType.Video &&
@ -868,9 +865,7 @@ namespace MediaBrowser.Model.Dlna
if (options.AllowAudioStreamCopy) if (options.AllowAudioStreamCopy)
{ {
var audioCodecs = ContainerProfile.SplitValue(transcodingProfile.AudioCodec); if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec))
if (ContainerProfile.ContainsContainer(audioCodecs, audioCodec))
{ {
var appliedVideoConditions = options.Profile.CodecProfiles var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.VideoAudio && .Where(i => i.Type == CodecType.VideoAudio &&
@ -913,20 +908,18 @@ namespace MediaBrowser.Model.Dlna
string? audioCodec) string? audioCodec)
{ {
// Prefer matching video codecs // Prefer matching video codecs
var videoCodecs = ContainerProfile.SplitValue(videoCodec); var videoCodecs = ContainerHelper.Split(videoCodec).ToList();
if (videoCodecs.Count == 0 && videoStream is not null)
{
// Add the original codec if no codec is specified
videoCodecs.Add(videoStream.Codec);
}
// Enforce HLS video codec restrictions // Enforce HLS video codec restrictions
if (playlistItem.SubProtocol == MediaStreamProtocol.hls) if (playlistItem.SubProtocol == MediaStreamProtocol.hls)
{ {
videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToArray(); videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToList();
}
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
if (directVideoCodec is not null)
{
// merge directVideoCodec to videoCodecs
Array.Resize(ref videoCodecs, videoCodecs.Length + 1);
videoCodecs[^1] = directVideoCodec;
} }
playlistItem.VideoCodecs = videoCodecs; playlistItem.VideoCodecs = videoCodecs;
@ -950,22 +943,28 @@ namespace MediaBrowser.Model.Dlna
} }
// Prefer matching audio codecs, could do better here // Prefer matching audio codecs, could do better here
var audioCodecs = ContainerProfile.SplitValue(audioCodec); var audioCodecs = ContainerHelper.Split(audioCodec).ToList();
if (audioCodecs.Count == 0 && audioStream is not null)
{
// Add the original codec if no codec is specified
audioCodecs.Add(audioStream.Codec);
}
// Enforce HLS audio codec restrictions // Enforce HLS audio codec restrictions
if (playlistItem.SubProtocol == MediaStreamProtocol.hls) if (playlistItem.SubProtocol == MediaStreamProtocol.hls)
{ {
if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase)) if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase))
{ {
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToArray(); audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToList();
} }
else else
{ {
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToArray(); audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToList();
} }
} }
var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)).FirstOrDefault(); var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerHelper.ContainsContainer(audioCodecs, false, stream.Codec)).FirstOrDefault();
var directAudioStream = audioStreamWithSupportedCodec?.Channels is not null && audioStreamWithSupportedCodec.Channels.Value <= (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue) ? audioStreamWithSupportedCodec : null; var directAudioStream = audioStreamWithSupportedCodec?.Channels is not null && audioStreamWithSupportedCodec.Channels.Value <= (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue) ? audioStreamWithSupportedCodec : null;
@ -982,7 +981,8 @@ namespace MediaBrowser.Model.Dlna
{ {
audioStream = directAudioStream; audioStream = directAudioStream;
playlistItem.AudioStreamIndex = audioStream.Index; playlistItem.AudioStreamIndex = audioStream.Index;
playlistItem.AudioCodecs = audioCodecs = new[] { audioStream.Codec }; audioCodecs = [audioStream.Codec];
playlistItem.AudioCodecs = audioCodecs;
// Copy matching audio codec options // Copy matching audio codec options
playlistItem.AudioSampleRate = audioStream.SampleRate; playlistItem.AudioSampleRate = audioStream.SampleRate;
@ -1023,18 +1023,17 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video && .Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(videoCodecs, container, useSubContainer) && i.ContainsAnyCodec(playlistItem.VideoCodecs, container, useSubContainer) &&
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))) i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)))
// Reverse codec profiles for backward compatibility - first codec profile has higher priority // Reverse codec profiles for backward compatibility - first codec profile has higher priority
.Reverse(); .Reverse();
foreach (var condition in appliedVideoConditions)
foreach (var i in appliedVideoConditions)
{ {
foreach (var transcodingVideoCodec in videoCodecs) foreach (var transcodingVideoCodec in playlistItem.VideoCodecs)
{ {
if (i.ContainsAnyCodec(transcodingVideoCodec, container, useSubContainer)) if (condition.ContainsAnyCodec(transcodingVideoCodec, container, useSubContainer))
{ {
ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, true); ApplyTranscodingConditions(playlistItem, condition.Conditions, transcodingVideoCodec, true, true);
continue; continue;
} }
} }
@ -1055,14 +1054,14 @@ namespace MediaBrowser.Model.Dlna
var appliedAudioConditions = options.Profile.CodecProfiles var appliedAudioConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.VideoAudio && .Where(i => i.Type == CodecType.VideoAudio &&
i.ContainsAnyCodec(audioCodecs, container) && i.ContainsAnyCodec(playlistItem.AudioCodecs, container) &&
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))) i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)))
// Reverse codec profiles for backward compatibility - first codec profile has higher priority // Reverse codec profiles for backward compatibility - first codec profile has higher priority
.Reverse(); .Reverse();
foreach (var codecProfile in appliedAudioConditions) foreach (var codecProfile in appliedAudioConditions)
{ {
foreach (var transcodingAudioCodec in audioCodecs) foreach (var transcodingAudioCodec in playlistItem.AudioCodecs)
{ {
if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container)) if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container))
{ {
@ -1132,9 +1131,9 @@ namespace MediaBrowser.Model.Dlna
return 192000; return 192000;
} }
private static int GetAudioBitrate(long maxTotalBitrate, string[] targetAudioCodecs, MediaStream? audioStream, StreamInfo item) private static int GetAudioBitrate(long maxTotalBitrate, IReadOnlyList<string> targetAudioCodecs, MediaStream? audioStream, StreamInfo item)
{ {
string? targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0]; string? targetAudioCodec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0];
int? targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec); int? targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec);
@ -1159,8 +1158,8 @@ namespace MediaBrowser.Model.Dlna
&& audioStream.Channels.Value <= targetAudioChannels.Value && audioStream.Channels.Value <= targetAudioChannels.Value
&& !string.IsNullOrEmpty(audioStream.Codec) && !string.IsNullOrEmpty(audioStream.Codec)
&& targetAudioCodecs is not null && targetAudioCodecs is not null
&& targetAudioCodecs.Length > 0 && targetAudioCodecs.Count > 0
&& !Array.Exists(targetAudioCodecs, elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase))) && !targetAudioCodecs.Any(elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase)))
{ {
// Shift the bitrate if we're transcoding to a different audio codec. // Shift the bitrate if we're transcoding to a different audio codec.
defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, audioStream.Channels.Value); defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, audioStream.Channels.Value);
@ -1316,25 +1315,8 @@ namespace MediaBrowser.Model.Dlna
} }
} }
var rankings = new[] { TranscodeReason.VideoCodecNotSupported, VideoCodecReasons, TranscodeReason.AudioCodecNotSupported, AudioCodecReasons, ContainerReasons };
var rank = (ref TranscodeReason a) =>
{
var index = 1;
foreach (var flag in rankings)
{
var reason = a & flag;
if (reason != 0)
{
return index;
}
index++;
}
return index;
};
var containerSupported = false; var containerSupported = false;
TranscodeReason[] rankings = [TranscodeReason.VideoCodecNotSupported, VideoCodecReasons, TranscodeReason.AudioCodecNotSupported, AudioCodecReasons, ContainerReasons];
// Check DirectPlay profiles to see if it can be direct played // Check DirectPlay profiles to see if it can be direct played
var analyzedProfiles = profile.DirectPlayProfiles var analyzedProfiles = profile.DirectPlayProfiles
@ -1400,7 +1382,8 @@ namespace MediaBrowser.Model.Dlna
playMethod = PlayMethod.DirectStream; playMethod = PlayMethod.DirectStream;
} }
var ranked = rank(ref failureReasons); var ranked = GetRank(ref failureReasons, rankings);
return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked); return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
}) })
.OrderByDescending(analysis => analysis.Result.PlayMethod) .OrderByDescending(analysis => analysis.Result.PlayMethod)
@ -1475,7 +1458,7 @@ namespace MediaBrowser.Model.Dlna
/// <param name="playMethod">The <see cref="PlayMethod"/>.</param> /// <param name="playMethod">The <see cref="PlayMethod"/>.</param>
/// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/>.</param> /// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/>.</param>
/// <param name="outputContainer">The output container.</param> /// <param name="outputContainer">The output container.</param>
/// <param name="transcodingSubProtocol">The subtitle transoding protocol.</param> /// <param name="transcodingSubProtocol">The subtitle transcoding protocol.</param>
/// <returns>The normalized input container.</returns> /// <returns>The normalized input container.</returns>
public static SubtitleProfile GetSubtitleProfile( public static SubtitleProfile GetSubtitleProfile(
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
@ -1501,7 +1484,7 @@ namespace MediaBrowser.Model.Dlna
continue; continue;
} }
if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer)) if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
{ {
continue; continue;
} }
@ -1530,7 +1513,7 @@ namespace MediaBrowser.Model.Dlna
continue; continue;
} }
if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer)) if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
{ {
continue; continue;
} }
@ -1561,17 +1544,12 @@ namespace MediaBrowser.Model.Dlna
{ {
if (!string.IsNullOrEmpty(transcodingContainer)) if (!string.IsNullOrEmpty(transcodingContainer))
{ {
string[] normalizedContainers = ContainerProfile.SplitValue(transcodingContainer); if (ContainerHelper.ContainsContainer(transcodingContainer, "ts,mpegts,mp4"))
if (ContainerProfile.ContainsContainer(normalizedContainers, "ts")
|| ContainerProfile.ContainsContainer(normalizedContainers, "mpegts")
|| ContainerProfile.ContainsContainer(normalizedContainers, "mp4"))
{ {
return false; return false;
} }
if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv") if (ContainerHelper.ContainsContainer(transcodingContainer, "mkv,matroska"))
|| ContainerProfile.ContainsContainer(normalizedContainers, "matroska"))
{ {
return true; return true;
} }
@ -2274,5 +2252,22 @@ namespace MediaBrowser.Model.Dlna
return false; return false;
} }
private int GetRank(ref TranscodeReason a, TranscodeReason[] rankings)
{
var index = 1;
foreach (var flag in rankings)
{
var reason = a & flag;
if (reason != 0)
{
return index;
}
index++;
}
return index;
}
} }
} }

View File

@ -1,9 +1,6 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
@ -11,121 +8,290 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna;
{
/// <summary> /// <summary>
/// Class StreamInfo. /// Class holding information on a stream.
/// </summary> /// </summary>
public class StreamInfo public class StreamInfo
{ {
/// <summary>
/// Initializes a new instance of the <see cref="StreamInfo"/> class.
/// </summary>
public StreamInfo() public StreamInfo()
{ {
AudioCodecs = Array.Empty<string>(); AudioCodecs = [];
VideoCodecs = Array.Empty<string>(); VideoCodecs = [];
SubtitleCodecs = Array.Empty<string>(); SubtitleCodecs = [];
StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
} }
/// <summary>
/// Gets or sets the item id.
/// </summary>
/// <value>The item id.</value>
public Guid ItemId { get; set; } public Guid ItemId { get; set; }
/// <summary>
/// Gets or sets the play method.
/// </summary>
/// <value>The play method.</value>
public PlayMethod PlayMethod { get; set; } public PlayMethod PlayMethod { get; set; }
/// <summary>
/// Gets or sets the encoding context.
/// </summary>
/// <value>The encoding context.</value>
public EncodingContext Context { get; set; } public EncodingContext Context { get; set; }
/// <summary>
/// Gets or sets the media type.
/// </summary>
/// <value>The media type.</value>
public DlnaProfileType MediaType { get; set; } public DlnaProfileType MediaType { get; set; }
/// <summary>
/// Gets or sets the container.
/// </summary>
/// <value>The container.</value>
public string? Container { get; set; } public string? Container { get; set; }
/// <summary>
/// Gets or sets the sub protocol.
/// </summary>
/// <value>The sub protocol.</value>
public MediaStreamProtocol SubProtocol { get; set; } public MediaStreamProtocol SubProtocol { get; set; }
/// <summary>
/// Gets or sets the start position ticks.
/// </summary>
/// <value>The start position ticks.</value>
public long StartPositionTicks { get; set; } public long StartPositionTicks { get; set; }
/// <summary>
/// Gets or sets the segment length.
/// </summary>
/// <value>The segment length.</value>
public int? SegmentLength { get; set; } public int? SegmentLength { get; set; }
/// <summary>
/// Gets or sets the minimum segments count.
/// </summary>
/// <value>The minimum segments count.</value>
public int? MinSegments { get; set; } public int? MinSegments { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the stream can be broken on non-keyframes.
/// </summary>
public bool BreakOnNonKeyFrames { get; set; } public bool BreakOnNonKeyFrames { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the stream requires AVC.
/// </summary>
public bool RequireAvc { get; set; } public bool RequireAvc { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the stream requires AVC.
/// </summary>
public bool RequireNonAnamorphic { get; set; } public bool RequireNonAnamorphic { get; set; }
/// <summary>
/// Gets or sets a value indicating whether timestamps should be copied.
/// </summary>
public bool CopyTimestamps { get; set; } public bool CopyTimestamps { get; set; }
/// <summary>
/// Gets or sets a value indicating whether timestamps should be copied.
/// </summary>
public bool EnableMpegtsM2TsMode { get; set; } public bool EnableMpegtsM2TsMode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the subtitle manifest is enabled.
/// </summary>
public bool EnableSubtitlesInManifest { get; set; } public bool EnableSubtitlesInManifest { get; set; }
public string[] AudioCodecs { get; set; } /// <summary>
/// Gets or sets the audio codecs.
/// </summary>
/// <value>The audio codecs.</value>
public IReadOnlyList<string> AudioCodecs { get; set; }
public string[] VideoCodecs { get; set; } /// <summary>
/// Gets or sets the video codecs.
/// </summary>
/// <value>The video codecs.</value>
public IReadOnlyList<string> VideoCodecs { get; set; }
/// <summary>
/// Gets or sets the audio stream index.
/// </summary>
/// <value>The audio stream index.</value>
public int? AudioStreamIndex { get; set; } public int? AudioStreamIndex { get; set; }
/// <summary>
/// Gets or sets the video stream index.
/// </summary>
/// <value>The subtitle stream index.</value>
public int? SubtitleStreamIndex { get; set; } public int? SubtitleStreamIndex { get; set; }
/// <summary>
/// Gets or sets the maximum transcoding audio channels.
/// </summary>
/// <value>The maximum transcoding audio channels.</value>
public int? TranscodingMaxAudioChannels { get; set; } public int? TranscodingMaxAudioChannels { get; set; }
/// <summary>
/// Gets or sets the global maximum audio channels.
/// </summary>
/// <value>The global maximum audio channels.</value>
public int? GlobalMaxAudioChannels { get; set; } public int? GlobalMaxAudioChannels { get; set; }
/// <summary>
/// Gets or sets the audio bitrate.
/// </summary>
/// <value>The audio bitrate.</value>
public int? AudioBitrate { get; set; } public int? AudioBitrate { get; set; }
/// <summary>
/// Gets or sets the audio sample rate.
/// </summary>
/// <value>The audio sample rate.</value>
public int? AudioSampleRate { get; set; } public int? AudioSampleRate { get; set; }
/// <summary>
/// Gets or sets the video bitrate.
/// </summary>
/// <value>The video bitrate.</value>
public int? VideoBitrate { get; set; } public int? VideoBitrate { get; set; }
/// <summary>
/// Gets or sets the maximum output width.
/// </summary>
/// <value>The output width.</value>
public int? MaxWidth { get; set; } public int? MaxWidth { get; set; }
/// <summary>
/// Gets or sets the maximum output height.
/// </summary>
/// <value>The maximum output height.</value>
public int? MaxHeight { get; set; } public int? MaxHeight { get; set; }
/// <summary>
/// Gets or sets the maximum framerate.
/// </summary>
/// <value>The maximum framerate.</value>
public float? MaxFramerate { get; set; } public float? MaxFramerate { get; set; }
/// <summary>
/// Gets or sets the device profile.
/// </summary>
/// <value>The device profile.</value>
public required DeviceProfile DeviceProfile { get; set; } public required DeviceProfile DeviceProfile { get; set; }
/// <summary>
/// Gets or sets the device profile id.
/// </summary>
/// <value>The device profile id.</value>
public string? DeviceProfileId { get; set; } public string? DeviceProfileId { get; set; }
/// <summary>
/// Gets or sets the device id.
/// </summary>
/// <value>The device id.</value>
public string? DeviceId { get; set; } public string? DeviceId { get; set; }
/// <summary>
/// Gets or sets the runtime ticks.
/// </summary>
/// <value>The runtime ticks.</value>
public long? RunTimeTicks { get; set; } public long? RunTimeTicks { get; set; }
/// <summary>
/// Gets or sets the transcode seek info.
/// </summary>
/// <value>The transcode seek info.</value>
public TranscodeSeekInfo TranscodeSeekInfo { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
/// <summary>
/// Gets or sets a value indicating whether content length should be estimated.
/// </summary>
public bool EstimateContentLength { get; set; } public bool EstimateContentLength { get; set; }
/// <summary>
/// Gets or sets the media source info.
/// </summary>
/// <value>The media source info.</value>
public MediaSourceInfo? MediaSource { get; set; } public MediaSourceInfo? MediaSource { get; set; }
public string[] SubtitleCodecs { get; set; } /// <summary>
/// Gets or sets the subtitle codecs.
/// </summary>
/// <value>The subtitle codecs.</value>
public IReadOnlyList<string> SubtitleCodecs { get; set; }
/// <summary>
/// Gets or sets the subtitle delivery method.
/// </summary>
/// <value>The subtitle delivery method.</value>
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
/// <summary>
/// Gets or sets the subtitle format.
/// </summary>
/// <value>The subtitle format.</value>
public string? SubtitleFormat { get; set; } public string? SubtitleFormat { get; set; }
/// <summary>
/// Gets or sets the play session id.
/// </summary>
/// <value>The play session id.</value>
public string? PlaySessionId { get; set; } public string? PlaySessionId { get; set; }
/// <summary>
/// Gets or sets the transcode reasons.
/// </summary>
/// <value>The transcode reasons.</value>
public TranscodeReason TranscodeReasons { get; set; } public TranscodeReason TranscodeReasons { get; set; }
/// <summary>
/// Gets the stream options.
/// </summary>
/// <value>The stream options.</value>
public Dictionary<string, string> StreamOptions { get; private set; } public Dictionary<string, string> StreamOptions { get; private set; }
/// <summary>
/// Gets the media source id.
/// </summary>
/// <value>The media source id.</value>
public string? MediaSourceId => MediaSource?.Id; public string? MediaSourceId => MediaSource?.Id;
/// <summary>
/// Gets or sets a value indicating whether audio VBR encoding is enabled.
/// </summary>
public bool EnableAudioVbrEncoding { get; set; } public bool EnableAudioVbrEncoding { get; set; }
/// <summary>
/// Gets a value indicating whether the stream is direct.
/// </summary>
public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay) public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay)
&& PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay; && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay;
/// <summary> /// <summary>
/// Gets the audio stream that will be used. /// Gets the audio stream that will be used in the output stream.
/// </summary> /// </summary>
/// <value>The audio stream.</value>
public MediaStream? TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex); public MediaStream? TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex);
/// <summary> /// <summary>
/// Gets the video stream that will be used. /// Gets the video stream that will be used in the output stream.
/// </summary> /// </summary>
/// <value>The video stream.</value>
public MediaStream? TargetVideoStream => MediaSource?.VideoStream; public MediaStream? TargetVideoStream => MediaSource?.VideoStream;
/// <summary> /// <summary>
/// Gets the audio sample rate that will be in the output stream. /// Gets the audio sample rate that will be in the output stream.
/// </summary> /// </summary>
/// <value>The target audio sample rate.</value>
public int? TargetAudioSampleRate public int? TargetAudioSampleRate
{ {
get get
@ -138,8 +304,9 @@ namespace MediaBrowser.Model.Dlna
} }
/// <summary> /// <summary>
/// Gets the audio sample rate that will be in the output stream. /// Gets the audio bit depth that will be in the output stream.
/// </summary> /// </summary>
/// <value>The target bit depth.</value>
public int? TargetAudioBitDepth public int? TargetAudioBitDepth
{ {
get get
@ -150,7 +317,7 @@ namespace MediaBrowser.Model.Dlna
} }
var targetAudioCodecs = TargetAudioCodec; var targetAudioCodecs = TargetAudioCodec;
var audioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0]; var audioCodec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0];
if (!string.IsNullOrEmpty(audioCodec)) if (!string.IsNullOrEmpty(audioCodec))
{ {
return GetTargetAudioBitDepth(audioCodec); return GetTargetAudioBitDepth(audioCodec);
@ -161,8 +328,9 @@ namespace MediaBrowser.Model.Dlna
} }
/// <summary> /// <summary>
/// Gets the audio sample rate that will be in the output stream. /// Gets the video bit depth that will be in the output stream.
/// </summary> /// </summary>
/// <value>The target video bit depth.</value>
public int? TargetVideoBitDepth public int? TargetVideoBitDepth
{ {
get get
@ -173,7 +341,7 @@ namespace MediaBrowser.Model.Dlna
} }
var targetVideoCodecs = TargetVideoCodec; var targetVideoCodecs = TargetVideoCodec;
var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0]; var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
if (!string.IsNullOrEmpty(videoCodec)) if (!string.IsNullOrEmpty(videoCodec))
{ {
return GetTargetVideoBitDepth(videoCodec); return GetTargetVideoBitDepth(videoCodec);
@ -184,7 +352,7 @@ namespace MediaBrowser.Model.Dlna
} }
/// <summary> /// <summary>
/// Gets the target reference frames. /// Gets the target reference frames that will be in the output stream.
/// </summary> /// </summary>
/// <value>The target reference frames.</value> /// <value>The target reference frames.</value>
public int? TargetRefFrames public int? TargetRefFrames
@ -197,7 +365,7 @@ namespace MediaBrowser.Model.Dlna
} }
var targetVideoCodecs = TargetVideoCodec; var targetVideoCodecs = TargetVideoCodec;
var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0]; var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
if (!string.IsNullOrEmpty(videoCodec)) if (!string.IsNullOrEmpty(videoCodec))
{ {
return GetTargetRefFrames(videoCodec); return GetTargetRefFrames(videoCodec);
@ -208,8 +376,9 @@ namespace MediaBrowser.Model.Dlna
} }
/// <summary> /// <summary>
/// Gets the audio sample rate that will be in the output stream. /// Gets the target framerate that will be in the output stream.
/// </summary> /// </summary>
/// <value>The target framerate.</value>
public float? TargetFramerate public float? TargetFramerate
{ {
get get
@ -222,8 +391,9 @@ namespace MediaBrowser.Model.Dlna
} }
/// <summary> /// <summary>
/// Gets the audio sample rate that will be in the output stream. /// Gets the target video level that will be in the output stream.
/// </summary> /// </summary>
/// <value>The target video level.</value>
public double? TargetVideoLevel public double? TargetVideoLevel
{ {
get get
@ -234,7 +404,7 @@ namespace MediaBrowser.Model.Dlna
} }
var targetVideoCodecs = TargetVideoCodec; var targetVideoCodecs = TargetVideoCodec;
var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0]; var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
if (!string.IsNullOrEmpty(videoCodec)) if (!string.IsNullOrEmpty(videoCodec))
{ {
return GetTargetVideoLevel(videoCodec); return GetTargetVideoLevel(videoCodec);
@ -245,8 +415,9 @@ namespace MediaBrowser.Model.Dlna
} }
/// <summary> /// <summary>
/// Gets the audio sample rate that will be in the output stream. /// Gets the target packet length that will be in the output stream.
/// </summary> /// </summary>
/// <value>The target packet length.</value>
public int? TargetPacketLength public int? TargetPacketLength
{ {
get get
@ -259,8 +430,9 @@ namespace MediaBrowser.Model.Dlna
} }
/// <summary> /// <summary>
/// Gets the audio sample rate that will be in the output stream. /// Gets the target video profile that will be in the output stream.
/// </summary> /// </summary>
/// <value>The target video profile.</value>
public string? TargetVideoProfile public string? TargetVideoProfile
{ {
get get
@ -271,7 +443,7 @@ namespace MediaBrowser.Model.Dlna
} }
var targetVideoCodecs = TargetVideoCodec; var targetVideoCodecs = TargetVideoCodec;
var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0]; var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
if (!string.IsNullOrEmpty(videoCodec)) if (!string.IsNullOrEmpty(videoCodec))
{ {
return GetOption(videoCodec, "profile"); return GetOption(videoCodec, "profile");
@ -284,6 +456,7 @@ namespace MediaBrowser.Model.Dlna
/// <summary> /// <summary>
/// Gets the target video range type that will be in the output stream. /// Gets the target video range type that will be in the output stream.
/// </summary> /// </summary>
/// <value>The video range type.</value>
public VideoRangeType TargetVideoRangeType public VideoRangeType TargetVideoRangeType
{ {
get get
@ -294,7 +467,7 @@ namespace MediaBrowser.Model.Dlna
} }
var targetVideoCodecs = TargetVideoCodec; var targetVideoCodecs = TargetVideoCodec;
var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0]; var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
if (!string.IsNullOrEmpty(videoCodec) if (!string.IsNullOrEmpty(videoCodec)
&& Enum.TryParse(GetOption(videoCodec, "rangetype"), true, out VideoRangeType videoRangeType)) && Enum.TryParse(GetOption(videoCodec, "rangetype"), true, out VideoRangeType videoRangeType))
{ {
@ -308,7 +481,7 @@ namespace MediaBrowser.Model.Dlna
/// <summary> /// <summary>
/// Gets the target video codec tag. /// Gets the target video codec tag.
/// </summary> /// </summary>
/// <value>The target video codec tag.</value> /// <value>The video codec tag.</value>
public string? TargetVideoCodecTag public string? TargetVideoCodecTag
{ {
get get
@ -323,6 +496,7 @@ namespace MediaBrowser.Model.Dlna
/// <summary> /// <summary>
/// Gets the audio bitrate that will be in the output stream. /// Gets the audio bitrate that will be in the output stream.
/// </summary> /// </summary>
/// <value>The audio bitrate.</value>
public int? TargetAudioBitrate public int? TargetAudioBitrate
{ {
get get
@ -335,8 +509,9 @@ namespace MediaBrowser.Model.Dlna
} }
/// <summary> /// <summary>
/// Gets the audio channels that will be in the output stream. /// Gets the amount of audio channels that will be in the output stream.
/// </summary> /// </summary>
/// <value>The target audio channels.</value>
public int? TargetAudioChannels public int? TargetAudioChannels
{ {
get get
@ -347,7 +522,7 @@ namespace MediaBrowser.Model.Dlna
} }
var targetAudioCodecs = TargetAudioCodec; var targetAudioCodecs = TargetAudioCodec;
var codec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0]; var codec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0];
if (!string.IsNullOrEmpty(codec)) if (!string.IsNullOrEmpty(codec))
{ {
return GetTargetRefFrames(codec); return GetTargetRefFrames(codec);
@ -360,7 +535,8 @@ namespace MediaBrowser.Model.Dlna
/// <summary> /// <summary>
/// Gets the audio codec that will be in the output stream. /// Gets the audio codec that will be in the output stream.
/// </summary> /// </summary>
public string[] TargetAudioCodec /// <value>The audio codec.</value>
public IReadOnlyList<string> TargetAudioCodec
{ {
get get
{ {
@ -370,14 +546,14 @@ namespace MediaBrowser.Model.Dlna
if (IsDirectStream) if (IsDirectStream)
{ {
return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec }; return string.IsNullOrEmpty(inputCodec) ? [] : [inputCodec];
} }
foreach (string codec in AudioCodecs) foreach (string codec in AudioCodecs)
{ {
if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase)) if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{ {
return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec }; return string.IsNullOrEmpty(codec) ? [] : [codec];
} }
} }
@ -385,7 +561,11 @@ namespace MediaBrowser.Model.Dlna
} }
} }
public string[] TargetVideoCodec /// <summary>
/// Gets the video codec that will be in the output stream.
/// </summary>
/// <value>The target video codec.</value>
public IReadOnlyList<string> TargetVideoCodec
{ {
get get
{ {
@ -395,14 +575,14 @@ namespace MediaBrowser.Model.Dlna
if (IsDirectStream) if (IsDirectStream)
{ {
return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec }; return string.IsNullOrEmpty(inputCodec) ? [] : [inputCodec];
} }
foreach (string codec in VideoCodecs) foreach (string codec in VideoCodecs)
{ {
if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase)) if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{ {
return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec }; return string.IsNullOrEmpty(codec) ? [] : [codec];
} }
} }
@ -411,8 +591,9 @@ namespace MediaBrowser.Model.Dlna
} }
/// <summary> /// <summary>
/// Gets the audio channels that will be in the output stream. /// Gets the target size of the output stream.
/// </summary> /// </summary>
/// <value>The target size.</value>
public long? TargetSize public long? TargetSize
{ {
get get
@ -441,6 +622,10 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets the target video bitrate of the output stream.
/// </summary>
/// <value>The video bitrate.</value>
public int? TargetVideoBitrate public int? TargetVideoBitrate
{ {
get get
@ -453,6 +638,10 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets the target timestamp of the output stream.
/// </summary>
/// <value>The target timestamp.</value>
public TransportStreamTimestamp TargetTimestamp public TransportStreamTimestamp TargetTimestamp
{ {
get get
@ -467,8 +656,15 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets the target total bitrate of the output stream.
/// </summary>
/// <value>The target total bitrate.</value>
public int? TargetTotalBitrate => (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0); public int? TargetTotalBitrate => (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0);
/// <summary>
/// Gets a value indicating whether the output stream is anamorphic.
/// </summary>
public bool? IsTargetAnamorphic public bool? IsTargetAnamorphic
{ {
get get
@ -482,6 +678,9 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets a value indicating whether the output stream is interlaced.
/// </summary>
public bool? IsTargetInterlaced public bool? IsTargetInterlaced
{ {
get get
@ -492,7 +691,7 @@ namespace MediaBrowser.Model.Dlna
} }
var targetVideoCodecs = TargetVideoCodec; var targetVideoCodecs = TargetVideoCodec;
var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0]; var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
if (!string.IsNullOrEmpty(videoCodec)) if (!string.IsNullOrEmpty(videoCodec))
{ {
if (string.Equals(GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase)) if (string.Equals(GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
@ -505,6 +704,9 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets a value indicating whether the output stream is AVC.
/// </summary>
public bool? IsTargetAVC public bool? IsTargetAVC
{ {
get get
@ -518,6 +720,10 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets the target width of the output stream.
/// </summary>
/// <value>The target width.</value>
public int? TargetWidth public int? TargetWidth
{ {
get get
@ -537,6 +743,10 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets the target height of the output stream.
/// </summary>
/// <value>The target height.</value>
public int? TargetHeight public int? TargetHeight
{ {
get get
@ -556,6 +766,10 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets the target video stream count of the output stream.
/// </summary>
/// <value>The target video stream count.</value>
public int? TargetVideoStreamCount public int? TargetVideoStreamCount
{ {
get get
@ -569,6 +783,10 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Gets the target audio stream count of the output stream.
/// </summary>
/// <value>The target audio stream count.</value>
public int? TargetAudioStreamCount public int? TargetAudioStreamCount
{ {
get get
@ -582,6 +800,12 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Sets a stream option.
/// </summary>
/// <param name="qualifier">The qualifier.</param>
/// <param name="name">The name.</param>
/// <param name="value">The value.</param>
public void SetOption(string? qualifier, string name, string value) public void SetOption(string? qualifier, string name, string value)
{ {
if (string.IsNullOrEmpty(qualifier)) if (string.IsNullOrEmpty(qualifier))
@ -594,11 +818,22 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Sets a stream option.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="value">The value.</param>
public void SetOption(string name, string value) public void SetOption(string name, string value)
{ {
StreamOptions[name] = value; StreamOptions[name] = value;
} }
/// <summary>
/// Gets a stream option.
/// </summary>
/// <param name="qualifier">The qualifier.</param>
/// <param name="name">The name.</param>
/// <returns>The value.</returns>
public string? GetOption(string? qualifier, string name) public string? GetOption(string? qualifier, string name)
{ {
var value = GetOption(qualifier + "-" + name); var value = GetOption(qualifier + "-" + name);
@ -611,6 +846,11 @@ namespace MediaBrowser.Model.Dlna
return value; return value;
} }
/// <summary>
/// Gets a stream option.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The value.</returns>
public string? GetOption(string name) public string? GetOption(string name)
{ {
if (StreamOptions.TryGetValue(name, out var value)) if (StreamOptions.TryGetValue(name, out var value))
@ -621,11 +861,17 @@ namespace MediaBrowser.Model.Dlna
return null; return null;
} }
/// <summary>
/// Returns this output stream URL for this class.
/// </summary>
/// <param name="baseUrl">The base Url.</param>
/// <param name="accessToken">The access Token.</param>
/// <returns>A querystring representation of this object.</returns>
public string ToUrl(string baseUrl, string? accessToken) public string ToUrl(string baseUrl, string? accessToken)
{ {
ArgumentException.ThrowIfNullOrEmpty(baseUrl); ArgumentException.ThrowIfNullOrEmpty(baseUrl);
var list = new List<string>(); List<string> list = [];
foreach (NameValuePair pair in BuildParams(this, accessToken)) foreach (NameValuePair pair in BuildParams(this, accessToken))
{ {
if (string.IsNullOrEmpty(pair.Value)) if (string.IsNullOrEmpty(pair.Value))
@ -688,15 +934,15 @@ namespace MediaBrowser.Model.Dlna
return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
} }
private static IEnumerable<NameValuePair> BuildParams(StreamInfo item, string? accessToken) private static List<NameValuePair> BuildParams(StreamInfo item, string? accessToken)
{ {
var list = new List<NameValuePair>(); List<NameValuePair> list = [];
string audioCodecs = item.AudioCodecs.Length == 0 ? string audioCodecs = item.AudioCodecs.Count == 0 ?
string.Empty : string.Empty :
string.Join(',', item.AudioCodecs); string.Join(',', item.AudioCodecs);
string videoCodecs = item.VideoCodecs.Length == 0 ? string videoCodecs = item.VideoCodecs.Count == 0 ?
string.Empty : string.Empty :
string.Join(',', item.VideoCodecs); string.Join(',', item.VideoCodecs);
@ -776,7 +1022,7 @@ namespace MediaBrowser.Model.Dlna
list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty)); list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty));
string subtitleCodecs = item.SubtitleCodecs.Length == 0 ? string subtitleCodecs = item.SubtitleCodecs.Count == 0 ?
string.Empty : string.Empty :
string.Join(",", item.SubtitleCodecs); string.Join(",", item.SubtitleCodecs);
@ -818,19 +1064,36 @@ namespace MediaBrowser.Model.Dlna
return list; return list;
} }
/// <summary>
/// Gets the subtitle profiles.
/// </summary>
/// <param name="transcoderSupport">The transcoder support.</param>
/// <param name="includeSelectedTrackOnly">If only the selected track should be included.</param>
/// <param name="baseUrl">The base URL.</param>
/// <param name="accessToken">The access token.</param>
/// <returns>The <see cref="SubtitleStreamInfo"/> of the profiles.</returns>
public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string? accessToken) public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string? accessToken)
{ {
return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken); return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
} }
/// <summary>
/// Gets the subtitle profiles.
/// </summary>
/// <param name="transcoderSupport">The transcoder support.</param>
/// <param name="includeSelectedTrackOnly">If only the selected track should be included.</param>
/// <param name="enableAllProfiles">If all profiles are enabled.</param>
/// <param name="baseUrl">The base URL.</param>
/// <param name="accessToken">The access token.</param>
/// <returns>The <see cref="SubtitleStreamInfo"/> of the profiles.</returns>
public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string? accessToken) public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string? accessToken)
{ {
if (MediaSource is null) if (MediaSource is null)
{ {
return Enumerable.Empty<SubtitleStreamInfo>(); return [];
} }
var list = new List<SubtitleStreamInfo>(); List<SubtitleStreamInfo> list = [];
// HLS will preserve timestamps so we can just grab the full subtitle stream // HLS will preserve timestamps so we can just grab the full subtitle stream
long startPositionTicks = SubProtocol == MediaStreamProtocol.hls long startPositionTicks = SubProtocol == MediaStreamProtocol.hls
@ -936,6 +1199,11 @@ namespace MediaBrowser.Model.Dlna
return info; return info;
} }
/// <summary>
/// Gets the target video bit depth.
/// </summary>
/// <param name="codec">The codec.</param>
/// <returns>The target video bit depth.</returns>
public int? GetTargetVideoBitDepth(string? codec) public int? GetTargetVideoBitDepth(string? codec)
{ {
var value = GetOption(codec, "videobitdepth"); var value = GetOption(codec, "videobitdepth");
@ -948,6 +1216,11 @@ namespace MediaBrowser.Model.Dlna
return null; return null;
} }
/// <summary>
/// Gets the target audio bit depth.
/// </summary>
/// <param name="codec">The codec.</param>
/// <returns>The target audio bit depth.</returns>
public int? GetTargetAudioBitDepth(string? codec) public int? GetTargetAudioBitDepth(string? codec)
{ {
var value = GetOption(codec, "audiobitdepth"); var value = GetOption(codec, "audiobitdepth");
@ -960,6 +1233,11 @@ namespace MediaBrowser.Model.Dlna
return null; return null;
} }
/// <summary>
/// Gets the target video level.
/// </summary>
/// <param name="codec">The codec.</param>
/// <returns>The target video level.</returns>
public double? GetTargetVideoLevel(string? codec) public double? GetTargetVideoLevel(string? codec)
{ {
var value = GetOption(codec, "level"); var value = GetOption(codec, "level");
@ -972,6 +1250,11 @@ namespace MediaBrowser.Model.Dlna
return null; return null;
} }
/// <summary>
/// Gets the target reference frames.
/// </summary>
/// <param name="codec">The codec.</param>
/// <returns>The target reference frames.</returns>
public int? GetTargetRefFrames(string? codec) public int? GetTargetRefFrames(string? codec)
{ {
var value = GetOption(codec, "maxrefframes"); var value = GetOption(codec, "maxrefframes");
@ -984,6 +1267,11 @@ namespace MediaBrowser.Model.Dlna
return null; return null;
} }
/// <summary>
/// Gets the target audio channels.
/// </summary>
/// <param name="codec">The codec.</param>
/// <returns>The target audio channels.</returns>
public int? GetTargetAudioChannels(string? codec) public int? GetTargetAudioChannels(string? codec)
{ {
var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels; var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
@ -1002,6 +1290,12 @@ namespace MediaBrowser.Model.Dlna
return defaultValue; return defaultValue;
} }
/// <summary>
/// Gets the media stream count.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="limit">The limit.</param>
/// <returns>The media stream count.</returns>
private int? GetMediaStreamCount(MediaStreamType type, int limit) private int? GetMediaStreamCount(MediaStreamType type, int limit)
{ {
var count = MediaSource?.GetStreamCount(type); var count = MediaSource?.GetStreamCount(type);
@ -1014,4 +1308,3 @@ namespace MediaBrowser.Model.Dlna
return count; return count;
} }
} }
}

View File

@ -1,34 +1,50 @@
#nullable disable #nullable disable
#pragma warning disable CS1591
using System;
using System.Xml.Serialization; using System.Xml.Serialization;
using Jellyfin.Extensions; using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna;
{
/// <summary>
/// A class for subtitle profile information.
/// </summary>
public class SubtitleProfile public class SubtitleProfile
{ {
/// <summary>
/// Gets or sets the format.
/// </summary>
[XmlAttribute("format")] [XmlAttribute("format")]
public string Format { get; set; } public string Format { get; set; }
/// <summary>
/// Gets or sets the delivery method.
/// </summary>
[XmlAttribute("method")] [XmlAttribute("method")]
public SubtitleDeliveryMethod Method { get; set; } public SubtitleDeliveryMethod Method { get; set; }
/// <summary>
/// Gets or sets the DIDL mode.
/// </summary>
[XmlAttribute("didlMode")] [XmlAttribute("didlMode")]
public string DidlMode { get; set; } public string DidlMode { get; set; }
/// <summary>
/// Gets or sets the language.
/// </summary>
[XmlAttribute("language")] [XmlAttribute("language")]
public string Language { get; set; } public string Language { get; set; }
/// <summary>
/// Gets or sets the container.
/// </summary>
[XmlAttribute("container")] [XmlAttribute("container")]
public string Container { get; set; } public string Container { get; set; }
public string[] GetLanguages() /// <summary>
{ /// Checks if a language is supported.
return ContainerProfile.SplitValue(Language); /// </summary>
} /// <param name="subLanguage">The language to check for support.</param>
/// <returns><c>true</c> if supported.</returns>
public bool SupportsLanguage(string subLanguage) public bool SupportsLanguage(string subLanguage)
{ {
if (string.IsNullOrEmpty(Language)) if (string.IsNullOrEmpty(Language))
@ -41,8 +57,6 @@ namespace MediaBrowser.Model.Dlna
subLanguage = "und"; subLanguage = "und";
} }
var languages = GetLanguages(); return ContainerHelper.ContainsContainer(Language, subLanguage);
return languages.Length == 0 || languages.Contains(subLanguage, StringComparison.OrdinalIgnoreCase);
}
} }
} }

View File

@ -1,82 +1,130 @@
#pragma warning disable CS1591
using System;
using System.ComponentModel; using System.ComponentModel;
using System.Xml.Serialization; using System.Xml.Serialization;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna;
{
/// <summary>
/// A class for transcoding profile information.
/// </summary>
public class TranscodingProfile public class TranscodingProfile
{ {
/// <summary>
/// Initializes a new instance of the <see cref="TranscodingProfile" /> class.
/// </summary>
public TranscodingProfile() public TranscodingProfile()
{ {
Conditions = Array.Empty<ProfileCondition>(); Conditions = [];
} }
/// <summary>
/// Gets or sets the container.
/// </summary>
[XmlAttribute("container")] [XmlAttribute("container")]
public string Container { get; set; } = string.Empty; public string Container { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the DLNA profile type.
/// </summary>
[XmlAttribute("type")] [XmlAttribute("type")]
public DlnaProfileType Type { get; set; } public DlnaProfileType Type { get; set; }
/// <summary>
/// Gets or sets the video codec.
/// </summary>
[XmlAttribute("videoCodec")] [XmlAttribute("videoCodec")]
public string VideoCodec { get; set; } = string.Empty; public string VideoCodec { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the audio codec.
/// </summary>
[XmlAttribute("audioCodec")] [XmlAttribute("audioCodec")]
public string AudioCodec { get; set; } = string.Empty; public string AudioCodec { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the protocol.
/// </summary>
[XmlAttribute("protocol")] [XmlAttribute("protocol")]
public MediaStreamProtocol Protocol { get; set; } = MediaStreamProtocol.http; public MediaStreamProtocol Protocol { get; set; } = MediaStreamProtocol.http;
/// <summary>
/// Gets or sets a value indicating whether the content length should be estimated.
/// </summary>
[DefaultValue(false)] [DefaultValue(false)]
[XmlAttribute("estimateContentLength")] [XmlAttribute("estimateContentLength")]
public bool EstimateContentLength { get; set; } public bool EstimateContentLength { get; set; }
/// <summary>
/// Gets or sets a value indicating whether M2TS mode is enabled.
/// </summary>
[DefaultValue(false)] [DefaultValue(false)]
[XmlAttribute("enableMpegtsM2TsMode")] [XmlAttribute("enableMpegtsM2TsMode")]
public bool EnableMpegtsM2TsMode { get; set; } public bool EnableMpegtsM2TsMode { get; set; }
/// <summary>
/// Gets or sets the transcoding seek info mode.
/// </summary>
[DefaultValue(TranscodeSeekInfo.Auto)] [DefaultValue(TranscodeSeekInfo.Auto)]
[XmlAttribute("transcodeSeekInfo")] [XmlAttribute("transcodeSeekInfo")]
public TranscodeSeekInfo TranscodeSeekInfo { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
/// <summary>
/// Gets or sets a value indicating whether timestamps should be copied.
/// </summary>
[DefaultValue(false)] [DefaultValue(false)]
[XmlAttribute("copyTimestamps")] [XmlAttribute("copyTimestamps")]
public bool CopyTimestamps { get; set; } public bool CopyTimestamps { get; set; }
/// <summary>
/// Gets or sets the encoding context.
/// </summary>
[DefaultValue(EncodingContext.Streaming)] [DefaultValue(EncodingContext.Streaming)]
[XmlAttribute("context")] [XmlAttribute("context")]
public EncodingContext Context { get; set; } public EncodingContext Context { get; set; }
/// <summary>
/// Gets or sets a value indicating whether subtitles are allowed in the manifest.
/// </summary>
[DefaultValue(false)] [DefaultValue(false)]
[XmlAttribute("enableSubtitlesInManifest")] [XmlAttribute("enableSubtitlesInManifest")]
public bool EnableSubtitlesInManifest { get; set; } public bool EnableSubtitlesInManifest { get; set; }
/// <summary>
/// Gets or sets the maximum audio channels.
/// </summary>
[XmlAttribute("maxAudioChannels")] [XmlAttribute("maxAudioChannels")]
public string? MaxAudioChannels { get; set; } public string? MaxAudioChannels { get; set; }
/// <summary>
/// Gets or sets the minimum amount of segments.
/// </summary>
[DefaultValue(0)] [DefaultValue(0)]
[XmlAttribute("minSegments")] [XmlAttribute("minSegments")]
public int MinSegments { get; set; } public int MinSegments { get; set; }
/// <summary>
/// Gets or sets the segment length.
/// </summary>
[DefaultValue(0)] [DefaultValue(0)]
[XmlAttribute("segmentLength")] [XmlAttribute("segmentLength")]
public int SegmentLength { get; set; } public int SegmentLength { get; set; }
/// <summary>
/// Gets or sets a value indicating whether breaking the video stream on non-keyframes is supported.
/// </summary>
[DefaultValue(false)] [DefaultValue(false)]
[XmlAttribute("breakOnNonKeyFrames")] [XmlAttribute("breakOnNonKeyFrames")]
public bool BreakOnNonKeyFrames { get; set; } public bool BreakOnNonKeyFrames { get; set; }
/// <summary>
/// Gets or sets the profile conditions.
/// </summary>
public ProfileCondition[] Conditions { get; set; } public ProfileCondition[] Conditions { get; set; }
/// <summary>
/// Gets or sets a value indicating whether variable bitrate encoding is supported.
/// </summary>
[DefaultValue(true)] [DefaultValue(true)]
[XmlAttribute("enableAudioVbrEncoding")] [XmlAttribute("enableAudioVbrEncoding")]
public bool EnableAudioVbrEncoding { get; set; } = true; public bool EnableAudioVbrEncoding { get; set; } = true;
public string[] GetAudioCodecs()
{
return ContainerProfile.SplitValue(AudioCodec);
}
}
} }

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using Jellyfin.Extensions;
namespace MediaBrowser.Model.Extensions;
/// <summary>
/// Defines the <see cref="ContainerHelper"/> class.
/// </summary>
public static class ContainerHelper
{
/// <summary>
/// Compares two containers, returning true if an item in <paramref name="inputContainer"/> exists
/// in <paramref name="profileContainers"/>.
/// </summary>
/// <param name="profileContainers">The comma-delimited string being searched.
/// If the parameter begins with the <c>-</c> character, the operation is reversed.</param>
/// <param name="inputContainer">The comma-delimited string being matched.</param>
/// <returns>The result of the operation.</returns>
public static bool ContainsContainer(string? profileContainers, string? inputContainer)
{
var isNegativeList = false;
if (profileContainers != null && profileContainers.StartsWith('-'))
{
isNegativeList = true;
profileContainers = profileContainers[1..];
}
return ContainsContainer(profileContainers, isNegativeList, inputContainer);
}
/// <summary>
/// Compares two containers, returning true if an item in <paramref name="inputContainer"/> exists
/// in <paramref name="profileContainers"/>.
/// </summary>
/// <param name="profileContainers">The comma-delimited string being searched.
/// If the parameter begins with the <c>-</c> character, the operation is reversed.</param>
/// <param name="inputContainer">The comma-delimited string being matched.</param>
/// <returns>The result of the operation.</returns>
public static bool ContainsContainer(string? profileContainers, ReadOnlySpan<char> inputContainer)
{
var isNegativeList = false;
if (profileContainers != null && profileContainers.StartsWith('-'))
{
isNegativeList = true;
profileContainers = profileContainers[1..];
}
return ContainsContainer(profileContainers, isNegativeList, inputContainer);
}
/// <summary>
/// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/>
/// does not exist in <paramref name="profileContainers"/>.
/// </summary>
/// <param name="profileContainers">The comma-delimited string being searched.</param>
/// <param name="isNegativeList">The boolean result to return if a match is not found.</param>
/// <param name="inputContainer">The comma-delimited string being matched.</param>
/// <returns>The result of the operation.</returns>
public static bool ContainsContainer(string? profileContainers, bool isNegativeList, string? inputContainer)
{
if (string.IsNullOrEmpty(inputContainer))
{
return isNegativeList;
}
return ContainsContainer(profileContainers, isNegativeList, inputContainer.AsSpan());
}
/// <summary>
/// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/>
/// does not exist in <paramref name="profileContainers"/>.
/// </summary>
/// <param name="profileContainers">The comma-delimited string being searched.</param>
/// <param name="isNegativeList">The boolean result to return if a match is not found.</param>
/// <param name="inputContainer">The comma-delimited string being matched.</param>
/// <returns>The result of the operation.</returns>
public static bool ContainsContainer(string? profileContainers, bool isNegativeList, ReadOnlySpan<char> inputContainer)
{
if (string.IsNullOrEmpty(profileContainers))
{
// Empty profiles always support all containers/codecs.
return true;
}
var allInputContainers = inputContainer.Split(',');
var allProfileContainers = profileContainers.SpanSplit(',');
foreach (var container in allInputContainers)
{
if (!container.IsEmpty)
{
foreach (var profile in allProfileContainers)
{
if (!profile.IsEmpty && container.Equals(profile, StringComparison.OrdinalIgnoreCase))
{
return !isNegativeList;
}
}
}
}
return isNegativeList;
}
/// <summary>
/// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/>
/// does not exist in <paramref name="profileContainers"/>.
/// </summary>
/// <param name="profileContainers">The profile containers being matched searched.</param>
/// <param name="isNegativeList">The boolean result to return if a match is not found.</param>
/// <param name="inputContainer">The comma-delimited string being matched.</param>
/// <returns>The result of the operation.</returns>
public static bool ContainsContainer(IReadOnlyList<string>? profileContainers, bool isNegativeList, string inputContainer)
{
if (profileContainers is null)
{
// Empty profiles always support all containers/codecs.
return true;
}
var allInputContainers = Split(inputContainer);
foreach (var container in allInputContainers)
{
foreach (var profile in profileContainers)
{
if (string.Equals(profile, container, StringComparison.OrdinalIgnoreCase))
{
return !isNegativeList;
}
}
}
return isNegativeList;
}
/// <summary>
/// Splits and input string.
/// </summary>
/// <param name="input">The input string.</param>
/// <returns>The result of the operation.</returns>
public static string[] Split(string? input)
{
return input?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? [];
}
}

View File

@ -316,7 +316,7 @@ namespace MediaBrowser.Providers.MediaInfo
genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray(); genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray();
} }
audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0 audio.Genres = options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0
? genres ? genres
: audio.Genres; : audio.Genres;
} }

View File

@ -0,0 +1,83 @@
using System;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Extensions;
using Xunit;
namespace Jellyfin.Model.Tests.Dlna;
public class ContainerHelperTests
{
private readonly ContainerProfile _emptyContainerProfile = new ContainerProfile();
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("mp4")]
public void ContainsContainer_EmptyContainerProfile_ReturnsTrue(string? containers)
{
Assert.True(_emptyContainerProfile.ContainsContainer(containers));
}
[Theory]
[InlineData("mp3,mpeg", "mp3")]
[InlineData("mp3,mpeg,avi", "mp3,avi")]
[InlineData("-mp3,mpeg", "avi")]
[InlineData("-mp3,mpeg,avi", "mp4,jpg")]
public void ContainsContainer_InList_ReturnsTrue(string container, string? extension)
{
Assert.True(ContainerHelper.ContainsContainer(container, extension));
}
[Theory]
[InlineData("mp3,mpeg", "avi")]
[InlineData("mp3,mpeg,avi", "mp4,jpg")]
[InlineData("mp3,mpeg", null)]
[InlineData("mp3,mpeg", "")]
[InlineData("-mp3,mpeg", "mp3")]
[InlineData("-mp3,mpeg,avi", "mpeg,avi")]
[InlineData(",mp3,", ",avi,")] // Empty values should be discarded
[InlineData("-,mp3,", ",mp3,")] // Empty values should be discarded
public void ContainsContainer_NotInList_ReturnsFalse(string container, string? extension)
{
Assert.False(ContainerHelper.ContainsContainer(container, extension));
if (extension is not null)
{
Assert.False(ContainerHelper.ContainsContainer(container, extension.AsSpan()));
}
}
[Theory]
[InlineData("mp3,mpeg", "mp3")]
[InlineData("mp3,mpeg,avi", "mp3,avi")]
[InlineData("-mp3,mpeg", "avi")]
[InlineData("-mp3,mpeg,avi", "mp4,jpg")]
public void ContainsContainer_InList_ReturnsTrue_SpanVersion(string container, string? extension)
{
Assert.True(ContainerHelper.ContainsContainer(container, extension.AsSpan()));
}
[Theory]
[InlineData(new string[] { "mp3", "mpeg" }, false, "mpeg")]
[InlineData(new string[] { "mp3", "mpeg", "avi" }, false, "avi")]
[InlineData(new string[] { "mp3", "", "avi" }, false, "mp3")]
[InlineData(new string[] { "mp3", "mpeg" }, true, "avi")]
[InlineData(new string[] { "mp3", "mpeg", "avi" }, true, "mkv")]
[InlineData(new string[] { "mp3", "", "avi" }, true, "")]
public void ContainsContainer_ThreeArgs_InList_ReturnsTrue(string[] containers, bool isNegativeList, string inputContainer)
{
Assert.True(ContainerHelper.ContainsContainer(containers, isNegativeList, inputContainer));
}
[Theory]
[InlineData(new string[] { "mp3", "mpeg" }, false, "avi")]
[InlineData(new string[] { "mp3", "mpeg", "avi" }, false, "mkv")]
[InlineData(new string[] { "mp3", "", "avi" }, false, "")]
[InlineData(new string[] { "mp3", "mpeg" }, true, "mpeg")]
[InlineData(new string[] { "mp3", "mpeg", "avi" }, true, "mp3")]
[InlineData(new string[] { "mp3", "", "avi" }, true, "avi")]
public void ContainsContainer_ThreeArgs_InList_ReturnsFalse(string[] containers, bool isNegativeList, string inputContainer)
{
Assert.False(ContainerHelper.ContainsContainer(containers, isNegativeList, inputContainer));
}
}

View File

@ -1,19 +0,0 @@
using MediaBrowser.Model.Dlna;
using Xunit;
namespace Jellyfin.Model.Tests.Dlna
{
public class ContainerProfileTests
{
private readonly ContainerProfile _emptyContainerProfile = new ContainerProfile();
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("mp4")]
public void ContainsContainer_EmptyContainerProfile_True(string? containers)
{
Assert.True(_emptyContainerProfile.ContainsContainer(containers));
}
}
}

View File

@ -389,18 +389,23 @@ namespace Jellyfin.Model.Tests
if (playMethod == PlayMethod.DirectPlay) if (playMethod == PlayMethod.DirectPlay)
{ {
// Check expected container // Check expected container
var containers = ContainerProfile.SplitValue(mediaSource.Container); var containers = mediaSource.Container.Split(',');
Assert.Contains(uri.Extension, containers);
// TODO: Test transcode too // TODO: Test transcode too
// Assert.Contains(uri.Extension, containers);
// Check expected video codec (1) // Check expected video codec (1)
if (targetVideoStream?.Codec is not null)
{
Assert.Contains(targetVideoStream?.Codec, streamInfo.TargetVideoCodec); Assert.Contains(targetVideoStream?.Codec, streamInfo.TargetVideoCodec);
Assert.Single(streamInfo.TargetVideoCodec); Assert.Single(streamInfo.TargetVideoCodec);
}
if (targetAudioStream?.Codec is not null)
{
// Check expected audio codecs (1) // Check expected audio codecs (1)
Assert.Contains(targetAudioStream?.Codec, streamInfo.TargetAudioCodec); Assert.Contains(targetAudioStream?.Codec, streamInfo.TargetAudioCodec);
Assert.Single(streamInfo.TargetAudioCodec); Assert.Single(streamInfo.TargetAudioCodec);
// Assert.Single(val.AudioCodecs); }
if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal)) if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal))
{ {