aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jellyfin.Server.Implementations/Devices/DeviceManager.cs4
-rw-r--r--MediaBrowser.Model/Dlna/CodecProfile.cs136
-rw-r--r--MediaBrowser.Model/Dlna/ContainerProfile.cs107
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfile.cs109
-rw-r--r--MediaBrowser.Model/Dlna/DirectPlayProfile.cs79
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs192
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs1675
-rw-r--r--MediaBrowser.Model/Dlna/SubtitleProfile.cs84
-rw-r--r--MediaBrowser.Model/Dlna/TranscodingProfile.cs196
-rw-r--r--MediaBrowser.Model/Extensions/ContainerHelper.cs145
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioFileProber.cs2
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/ContainerHelperTests.cs54
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs19
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs21
14 files changed, 1690 insertions, 1133 deletions
diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
index d7a46e2d5..415c04bbf 100644
--- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
+++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
@@ -135,8 +135,8 @@ namespace Jellyfin.Server.Implementations.Devices
{
IEnumerable<Device> devices = _devices.Values
.Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value))
- .Where(device => query.DeviceId == null || device.DeviceId == query.DeviceId)
- .Where(device => query.AccessToken == null || device.AccessToken == query.AccessToken)
+ .Where(device => query.DeviceId is null || device.DeviceId == query.DeviceId)
+ .Where(device => query.AccessToken is null || device.AccessToken == query.AccessToken)
.OrderBy(d => d.Id)
.ToList();
var count = devices.Count();
diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs
index 07c1a29a4..da34eddcd 100644
--- a/MediaBrowser.Model/Dlna/CodecProfile.cs
+++ b/MediaBrowser.Model/Dlna/CodecProfile.cs
@@ -1,74 +1,94 @@
-#nullable disable
-#pragma warning disable CS1591
-
using System;
+using System.Collections.Generic;
+using System.Linq;
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>();
- ApplyConditions = Array.Empty<ProfileCondition>();
- }
-
- [XmlAttribute("type")]
- public CodecType Type { get; set; }
-
- public ProfileCondition[] Conditions { get; set; }
-
- public ProfileCondition[] ApplyConditions { get; set; }
-
- [XmlAttribute("codec")]
- public string Codec { get; set; }
+ Conditions = [];
+ ApplyConditions = [];
+ }
- [XmlAttribute("container")]
- public string Container { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="CodecType"/> which this container must meet.
+ /// </summary>
+ [XmlAttribute("type")]
+ public CodecType Type { get; set; }
- [XmlAttribute("subcontainer")]
- public string SubContainer { get; set; }
+ /// <summary>
+ /// Gets or sets the list of <see cref="ProfileCondition"/> which this profile must meet.
+ /// </summary>
+ public ProfileCondition[] Conditions { get; set; }
- public string[] GetCodecs()
- {
- return ContainerProfile.SplitValue(Codec);
- }
+ /// <summary>
+ /// Gets or sets the list of <see cref="ProfileCondition"/> to apply if this profile is met.
+ /// </summary>
+ public ProfileCondition[] ApplyConditions { get; set; }
- private bool ContainsContainer(string container, bool useSubContainer = false)
- {
- var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
- return ContainerProfile.ContainsContainer(containerToCheck, container);
- }
+ /// <summary>
+ /// Gets or sets the codec(s) that this profile applies to.
+ /// </summary>
+ [XmlAttribute("codec")]
+ public string? Codec { get; set; }
- public bool ContainsAnyCodec(string codec, string container, bool useSubContainer = false)
- {
- return ContainsAnyCodec(ContainerProfile.SplitValue(codec), container, useSubContainer);
- }
+ /// <summary>
+ /// Gets or sets the container(s) which this profile will be applied to.
+ /// </summary>
+ [XmlAttribute("container")]
+ public string? Container { get; set; }
- public bool ContainsAnyCodec(string[] codec, string container, bool useSubContainer = false)
- {
- if (!ContainsContainer(container, useSubContainer))
- {
- return false;
- }
+ /// <summary>
+ /// Gets or sets the sub-container(s) which this profile will be applied to.
+ /// </summary>
+ [XmlAttribute("subcontainer")]
+ public string? SubContainer { get; set; }
- var codecs = GetCodecs();
- if (codecs.Length == 0)
- {
- return true;
- }
+ /// <summary>
+ /// Checks to see whether the codecs and containers contain the given parameters.
+ /// </summary>
+ /// <param name="codecs">The codecs 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(IReadOnlyList<string> codecs, string? container, bool useSubContainer = false)
+ {
+ var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
+ return ContainerHelper.ContainsContainer(containerToCheck, container) && codecs.Any(c => ContainerHelper.ContainsContainer(Codec, false, c));
+ }
- foreach (var val in codec)
- {
- if (codecs.Contains(val, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
+ /// <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(codec.AsSpan(), container, useSubContainer);
+ }
- return 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)
+ {
+ var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
+ return ContainerHelper.ContainsContainer(containerToCheck, container) && ContainerHelper.ContainsContainer(Codec, false, codec);
}
}
diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs
index 978004268..a42179907 100644
--- a/MediaBrowser.Model/Dlna/ContainerProfile.cs
+++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs
@@ -1,74 +1,49 @@
-#pragma warning disable CS1591
+#pragma warning disable CA1819 // Properties should not return arrays
using System;
+using System.Collections.Generic;
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")]
+ public DlnaProfileType Type { get; set; }
+
+ /// <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")]
+ public string? Container { get; set; }
+
+ /// <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)
{
- [XmlAttribute("type")]
- public DlnaProfileType Type { get; set; }
-
- public ProfileCondition[] Conditions { get; set; } = Array.Empty<ProfileCondition>();
-
- [XmlAttribute("container")]
- public string Container { get; set; } = string.Empty;
-
- public static string[] SplitValue(string? value)
- {
- if (string.IsNullOrEmpty(value))
- {
- 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;
- }
+ var containerToCheck = useSubContainer && string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase) ? SubContainer : Container;
+ return ContainerHelper.ContainsContainer(containerToCheck, container);
}
}
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index 2addebbfc..f68957622 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -1,74 +1,71 @@
#pragma warning disable CA1819 // Properties should not return arrays
using System;
-using System.Xml.Serialization;
-namespace MediaBrowser.Model.Dlna
+namespace MediaBrowser.Model.Dlna;
+
+/// <summary>
+/// A <see cref="DeviceProfile" /> represents a set of metadata which determines which content a certain device is able to play.
+/// <br/>
+/// Specifically, it defines the supported <see cref="ContainerProfiles">containers</see> and
+/// <see cref="CodecProfiles">codecs</see> (video and/or audio, including codec profiles and levels)
+/// the device is able to direct play (without transcoding or remuxing),
+/// as well as which <see cref="TranscodingProfiles">containers/codecs to transcode to</see> in case it isn't.
+/// </summary>
+public class DeviceProfile
{
/// <summary>
- /// A <see cref="DeviceProfile" /> represents a set of metadata which determines which content a certain device is able to play.
- /// <br/>
- /// Specifically, it defines the supported <see cref="ContainerProfiles">containers</see> and
- /// <see cref="CodecProfiles">codecs</see> (video and/or audio, including codec profiles and levels)
- /// the device is able to direct play (without transcoding or remuxing),
- /// as well as which <see cref="TranscodingProfiles">containers/codecs to transcode to</see> in case it isn't.
+ /// Gets or sets the name of this device profile. User profiles must have a unique name.
/// </summary>
- public class DeviceProfile
- {
- /// <summary>
- /// Gets or sets the name of this device profile.
- /// </summary>
- public string? Name { get; set; }
+ public string? Name { get; set; }
- /// <summary>
- /// Gets or sets the Id.
- /// </summary>
- [XmlIgnore]
- public string? Id { get; set; }
+ /// <summary>
+ /// Gets or sets the unique internal identifier.
+ /// </summary>
+ public Guid Id { get; set; }
- /// <summary>
- /// Gets or sets the maximum allowed bitrate for all streamed content.
- /// </summary>
- public int? MaxStreamingBitrate { get; set; } = 8000000;
+ /// <summary>
+ /// Gets or sets the maximum allowed bitrate for all streamed content.
+ /// </summary>
+ public int? MaxStreamingBitrate { get; set; } = 8000000;
- /// <summary>
- /// Gets or sets the maximum allowed bitrate for statically streamed content (= direct played files).
- /// </summary>
- public int? MaxStaticBitrate { get; set; } = 8000000;
+ /// <summary>
+ /// Gets or sets the maximum allowed bitrate for statically streamed content (= direct played files).
+ /// </summary>
+ public int? MaxStaticBitrate { get; set; } = 8000000;
- /// <summary>
- /// Gets or sets the maximum allowed bitrate for transcoded music streams.
- /// </summary>
- public int? MusicStreamingTranscodingBitrate { get; set; } = 128000;
+ /// <summary>
+ /// Gets or sets the maximum allowed bitrate for transcoded music streams.
+ /// </summary>
+ public int? MusicStreamingTranscodingBitrate { get; set; } = 128000;
- /// <summary>
- /// Gets or sets the maximum allowed bitrate for statically streamed (= direct played) music files.
- /// </summary>
- public int? MaxStaticMusicBitrate { get; set; } = 8000000;
+ /// <summary>
+ /// Gets or sets the maximum allowed bitrate for statically streamed (= direct played) music files.
+ /// </summary>
+ public int? MaxStaticMusicBitrate { get; set; } = 8000000;
- /// <summary>
- /// Gets or sets the direct play profiles.
- /// </summary>
- public DirectPlayProfile[] DirectPlayProfiles { get; set; } = Array.Empty<DirectPlayProfile>();
+ /// <summary>
+ /// Gets or sets the direct play profiles.
+ /// </summary>
+ public DirectPlayProfile[] DirectPlayProfiles { get; set; } = [];
- /// <summary>
- /// Gets or sets the transcoding profiles.
- /// </summary>
- public TranscodingProfile[] TranscodingProfiles { get; set; } = Array.Empty<TranscodingProfile>();
+ /// <summary>
+ /// Gets or sets the transcoding profiles.
+ /// </summary>
+ public TranscodingProfile[] TranscodingProfiles { get; set; } = [];
- /// <summary>
- /// Gets or sets the container profiles.
- /// </summary>
- public ContainerProfile[] ContainerProfiles { get; set; } = Array.Empty<ContainerProfile>();
+ /// <summary>
+ /// Gets or sets the container profiles. Failing to meet these optional conditions causes transcoding to occur.
+ /// </summary>
+ public ContainerProfile[] ContainerProfiles { get; set; } = [];
- /// <summary>
- /// Gets or sets the codec profiles.
- /// </summary>
- public CodecProfile[] CodecProfiles { get; set; } = Array.Empty<CodecProfile>();
+ /// <summary>
+ /// Gets or sets the codec profiles.
+ /// </summary>
+ public CodecProfile[] CodecProfiles { get; set; } = [];
- /// <summary>
- /// Gets or sets the subtitle profiles.
- /// </summary>
- public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty<SubtitleProfile>();
- }
+ /// <summary>
+ /// Gets or sets the subtitle profiles.
+ /// </summary>
+ public SubtitleProfile[] SubtitleProfiles { get; set; } = [];
}
diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
index f68235d86..438df3441 100644
--- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
+++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
@@ -1,36 +1,65 @@
-#pragma warning disable CS1591
-
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
- {
- [XmlAttribute("container")]
- public string? Container { get; set; }
+ /// <summary>
+ /// Gets or sets the container.
+ /// </summary>
+ [XmlAttribute("container")]
+ public string Container { get; set; } = string.Empty;
- [XmlAttribute("audioCodec")]
- public string? AudioCodec { get; set; }
+ /// <summary>
+ /// Gets or sets the audio codec.
+ /// </summary>
+ [XmlAttribute("audioCodec")]
+ public string? AudioCodec { get; set; }
- [XmlAttribute("videoCodec")]
- public string? VideoCodec { get; set; }
+ /// <summary>
+ /// Gets or sets the video codec.
+ /// </summary>
+ [XmlAttribute("videoCodec")]
+ public string? VideoCodec { get; set; }
- [XmlAttribute("type")]
- public DlnaProfileType Type { get; set; }
+ /// <summary>
+ /// Gets or sets the Dlna profile type.
+ /// </summary>
+ [XmlAttribute("type")]
+ public DlnaProfileType Type { get; set; }
- public bool SupportsContainer(string? container)
- {
- return ContainerProfile.ContainsContainer(Container, container);
- }
+ /// <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)
+ {
+ return ContainerHelper.ContainsContainer(Container, container);
+ }
- public bool SupportsVideoCodec(string? codec)
- {
- return Type == DlnaProfileType.Video && ContainerProfile.ContainsContainer(VideoCodec, codec);
- }
+ /// <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)
+ {
+ return Type == DlnaProfileType.Video && ContainerHelper.ContainsContainer(VideoCodec, codec);
+ }
- public bool SupportsAudioCodec(string? codec)
- {
- return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, 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)
+ {
+ // 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);
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index bf612f0ac..6fc7f796d 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -6,6 +6,7 @@ using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
@@ -27,9 +28,9 @@ namespace MediaBrowser.Model.Dlna
private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport;
- private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc", "vp9", "av1" };
- private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" };
- private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" };
+ private static readonly string[] _supportedHlsVideoCodecs = ["h264", "hevc", "vp9", "av1"];
+ private static readonly string[] _supportedHlsAudioCodecsTs = ["aac", "ac3", "eac3", "mp3"];
+ private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd"];
/// <summary>
/// Initializes a new instance of the <see cref="StreamBuilder"/> class.
@@ -51,7 +52,7 @@ namespace MediaBrowser.Model.Dlna
{
ValidateMediaOptions(options, false);
- var streams = new List<StreamInfo>();
+ List<StreamInfo> streams = [];
foreach (var mediaSource in options.MediaSources)
{
if (!(string.IsNullOrEmpty(options.MediaSourceId)
@@ -64,7 +65,7 @@ namespace MediaBrowser.Model.Dlna
if (streamInfo is not null)
{
streamInfo.DeviceId = options.DeviceId;
- streamInfo.DeviceProfileId = options.Profile.Id;
+ streamInfo.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture);
streams.Add(streamInfo);
}
}
@@ -129,7 +130,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlayMethod is PlayMethod.DirectStream)
{
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
// 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;
@@ -226,7 +227,7 @@ namespace MediaBrowser.Model.Dlna
? options.MediaSources
: 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)
{
var streamInfo = BuildVideoItem(mediaSourceInfo, options);
@@ -239,7 +240,7 @@ namespace MediaBrowser.Model.Dlna
foreach (var stream in streams)
{
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);
@@ -388,32 +389,33 @@ namespace MediaBrowser.Model.Dlna
/// <param name="type">The <see cref="DlnaProfileType"/>.</param>
/// <param name="playProfile">The <see cref="DirectPlayProfile"/> object to get the video stream from.</param>
/// <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);
-
- if (profile is not null)
+ var formats = ContainerHelper.Split(inputContainer);
+ var playProfiles = playProfile is null ? profile.DirectPlayProfiles : [playProfile];
+ foreach (var format in formats)
{
- var playProfiles = playProfile is null ? profile.DirectPlayProfiles : new[] { playProfile };
- 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))
- {
- return format;
- }
+ continue;
+ }
+
+ var formatStr = format.ToString();
+ if (directPlayProfile.SupportsContainer(formatStr))
+ {
+ return formatStr;
}
}
}
- return formats[0];
+ return inputContainer;
}
private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, MediaOptions options)
@@ -533,7 +535,6 @@ namespace MediaBrowser.Model.Dlna
private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
{
int highestScore = -1;
-
foreach (var stream in item.MediaStreams)
{
if (stream.Type == MediaStreamType.Subtitle
@@ -544,7 +545,7 @@ namespace MediaBrowser.Model.Dlna
}
}
- var topStreams = new List<MediaStream>();
+ List<MediaStream> topStreams = [];
foreach (var stream in item.MediaStreams)
{
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore)
@@ -623,8 +624,8 @@ namespace MediaBrowser.Model.Dlna
playlistItem.Container = container;
playlistItem.SubProtocol = protocol;
- playlistItem.VideoCodecs = new[] { item.VideoStream.Codec };
- playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec);
+ playlistItem.VideoCodecs = [item.VideoStream.Codec];
+ playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec);
}
private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options)
@@ -651,7 +652,7 @@ namespace MediaBrowser.Model.Dlna
}
// 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 (audioStream?.IsDefault == true)
@@ -702,7 +703,8 @@ namespace MediaBrowser.Model.Dlna
directPlayProfile = directPlayInfo.Profile;
playlistItem.PlayMethod = directPlay.Value;
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)
{
@@ -713,7 +715,7 @@ namespace MediaBrowser.Model.Dlna
{
playlistItem.AudioStreamIndex = audioStreamIndex;
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)
@@ -721,7 +723,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioStreamIndex = audioStream?.Index;
if (audioStream is not null)
{
- playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec);
+ playlistItem.AudioCodecs = ContainerHelper.Split(directPlayProfile?.AudioCodec);
}
SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile);
@@ -753,7 +755,7 @@ namespace MediaBrowser.Model.Dlna
{
// Can't direct play, find the transcoding profile
// 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)
{
@@ -781,7 +783,7 @@ namespace MediaBrowser.Model.Dlna
}
playlistItem.SubtitleFormat = subtitleProfile.Format;
- playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
+ playlistItem.SubtitleCodecs = [subtitleProfile.Format];
}
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
@@ -810,8 +812,6 @@ namespace MediaBrowser.Model.Dlna
MediaOptions options,
MediaStream? videoStream,
MediaStream? audioStream,
- IEnumerable<MediaStream> candidateAudioStreams,
- MediaStream? subtitleStream,
StreamInfo playlistItem)
{
if (!(item.SupportsTranscoding || item.SupportsDirectStream))
@@ -849,9 +849,7 @@ namespace MediaBrowser.Model.Dlna
if (options.AllowVideoStreamCopy)
{
- var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
-
- if (ContainerProfile.ContainsContainer(videoCodecs, videoCodec))
+ if (ContainerHelper.ContainsContainer(transcodingProfile.VideoCodec, videoCodec))
{
var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video &&
@@ -868,9 +866,7 @@ namespace MediaBrowser.Model.Dlna
if (options.AllowAudioStreamCopy)
{
- var audioCodecs = ContainerProfile.SplitValue(transcodingProfile.AudioCodec);
-
- if (ContainerProfile.ContainsContainer(audioCodecs, audioCodec))
+ if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec))
{
var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.VideoAudio &&
@@ -913,20 +909,18 @@ namespace MediaBrowser.Model.Dlna
string? audioCodec)
{
// Prefer matching video codecs
- var videoCodecs = ContainerProfile.SplitValue(videoCodec);
+ var videoCodecs = ContainerHelper.Split(videoCodec).ToList();
- // Enforce HLS video codec restrictions
- if (playlistItem.SubProtocol == MediaStreamProtocol.hls)
+ if (videoCodecs.Count == 0 && videoStream is not null)
{
- videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToArray();
+ // Add the original codec if no codec is specified
+ videoCodecs.Add(videoStream.Codec);
}
- var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
- if (directVideoCodec is not null)
+ // Enforce HLS video codec restrictions
+ if (playlistItem.SubProtocol == MediaStreamProtocol.hls)
{
- // merge directVideoCodec to videoCodecs
- Array.Resize(ref videoCodecs, videoCodecs.Length + 1);
- videoCodecs[^1] = directVideoCodec;
+ videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToList();
}
playlistItem.VideoCodecs = videoCodecs;
@@ -950,22 +944,28 @@ namespace MediaBrowser.Model.Dlna
}
// 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
if (playlistItem.SubProtocol == MediaStreamProtocol.hls)
{
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
{
- 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;
@@ -982,7 +982,8 @@ namespace MediaBrowser.Model.Dlna
{
audioStream = directAudioStream;
playlistItem.AudioStreamIndex = audioStream.Index;
- playlistItem.AudioCodecs = audioCodecs = new[] { audioStream.Codec };
+ audioCodecs = [audioStream.Codec];
+ playlistItem.AudioCodecs = audioCodecs;
// Copy matching audio codec options
playlistItem.AudioSampleRate = audioStream.SampleRate;
@@ -1023,18 +1024,17 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles
.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)))
// Reverse codec profiles for backward compatibility - first codec profile has higher priority
.Reverse();
-
- foreach (var i in appliedVideoConditions)
+ foreach (var condition 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;
}
}
@@ -1055,14 +1055,14 @@ namespace MediaBrowser.Model.Dlna
var appliedAudioConditions = options.Profile.CodecProfiles
.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)))
// Reverse codec profiles for backward compatibility - first codec profile has higher priority
.Reverse();
foreach (var codecProfile in appliedAudioConditions)
{
- foreach (var transcodingAudioCodec in audioCodecs)
+ foreach (var transcodingAudioCodec in playlistItem.AudioCodecs)
{
if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container))
{
@@ -1132,9 +1132,9 @@ namespace MediaBrowser.Model.Dlna
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);
@@ -1151,7 +1151,7 @@ namespace MediaBrowser.Model.Dlna
&& audioStream.Channels.HasValue
&& audioStream.Channels.Value > targetAudioChannels.Value)
{
- // Reduce the bitrate if we're downmixing.
+ // Reduce the bitrate if we're down mixing.
defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, targetAudioChannels);
}
else if (targetAudioChannels.HasValue
@@ -1159,8 +1159,8 @@ namespace MediaBrowser.Model.Dlna
&& audioStream.Channels.Value <= targetAudioChannels.Value
&& !string.IsNullOrEmpty(audioStream.Codec)
&& targetAudioCodecs is not null
- && targetAudioCodecs.Length > 0
- && !Array.Exists(targetAudioCodecs, elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase)))
+ && targetAudioCodecs.Count > 0
+ && !targetAudioCodecs.Any(elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase)))
{
// Shift the bitrate if we're transcoding to a different audio codec.
defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, audioStream.Channels.Value);
@@ -1299,7 +1299,7 @@ namespace MediaBrowser.Model.Dlna
!checkVideoConditions(codecProfile.ApplyConditions).Any())
.SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
- // Check audiocandidates profile conditions
+ // Check audio candidates profile conditions
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream));
TranscodeReason subtitleProfileReasons = 0;
@@ -1316,24 +1316,6 @@ 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;
// Check DirectPlay profiles to see if it can be direct played
@@ -1400,7 +1382,9 @@ namespace MediaBrowser.Model.Dlna
playMethod = PlayMethod.DirectStream;
}
- var ranked = rank(ref failureReasons);
+ TranscodeReason[] rankings = [TranscodeReason.VideoCodecNotSupported, VideoCodecReasons, TranscodeReason.AudioCodecNotSupported, AudioCodecReasons, ContainerReasons];
+ var ranked = GetRank(ref failureReasons, rankings);
+
return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
})
.OrderByDescending(analysis => analysis.Result.PlayMethod)
@@ -1475,7 +1459,7 @@ namespace MediaBrowser.Model.Dlna
/// <param name="playMethod">The <see cref="PlayMethod"/>.</param>
/// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/>.</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>
public static SubtitleProfile GetSubtitleProfile(
MediaSourceInfo mediaSource,
@@ -1501,7 +1485,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer))
+ if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
{
continue;
}
@@ -1530,7 +1514,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer))
+ if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
{
continue;
}
@@ -1561,17 +1545,12 @@ namespace MediaBrowser.Model.Dlna
{
if (!string.IsNullOrEmpty(transcodingContainer))
{
- string[] normalizedContainers = ContainerProfile.SplitValue(transcodingContainer);
-
- if (ContainerProfile.ContainsContainer(normalizedContainers, "ts")
- || ContainerProfile.ContainsContainer(normalizedContainers, "mpegts")
- || ContainerProfile.ContainsContainer(normalizedContainers, "mp4"))
+ if (ContainerHelper.ContainsContainer(transcodingContainer, "ts,mpegts,mp4"))
{
return false;
}
- if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv")
- || ContainerProfile.ContainsContainer(normalizedContainers, "matroska"))
+ if (ContainerHelper.ContainsContainer(transcodingContainer, "mkv,matroska"))
{
return true;
}
@@ -2274,5 +2253,22 @@ namespace MediaBrowser.Model.Dlna
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;
+ }
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 8232ee3fe..3be686088 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -1,9 +1,6 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
@@ -11,1007 +8,1303 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
-namespace MediaBrowser.Model.Dlna
+namespace MediaBrowser.Model.Dlna;
+
+/// <summary>
+/// Class holding information on a stream.
+/// </summary>
+public class StreamInfo
{
/// <summary>
- /// Class StreamInfo.
+ /// Initializes a new instance of the <see cref="StreamInfo"/> class.
/// </summary>
- public class StreamInfo
+ public StreamInfo()
{
- public StreamInfo()
- {
- AudioCodecs = Array.Empty<string>();
- VideoCodecs = Array.Empty<string>();
- SubtitleCodecs = Array.Empty<string>();
- StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- }
+ AudioCodecs = [];
+ VideoCodecs = [];
+ SubtitleCodecs = [];
+ StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ }
- public Guid ItemId { get; set; }
+ /// <summary>
+ /// Gets or sets the item id.
+ /// </summary>
+ /// <value>The item id.</value>
+ public Guid ItemId { get; set; }
- public PlayMethod PlayMethod { get; set; }
+ /// <summary>
+ /// Gets or sets the play method.
+ /// </summary>
+ /// <value>The play method.</value>
+ public PlayMethod PlayMethod { get; set; }
- public EncodingContext Context { get; set; }
+ /// <summary>
+ /// Gets or sets the encoding context.
+ /// </summary>
+ /// <value>The encoding context.</value>
+ public EncodingContext Context { get; set; }
- public DlnaProfileType MediaType { get; set; }
+ /// <summary>
+ /// Gets or sets the media type.
+ /// </summary>
+ /// <value>The media type.</value>
+ public DlnaProfileType MediaType { get; set; }
- public string? Container { get; set; }
+ /// <summary>
+ /// Gets or sets the container.
+ /// </summary>
+ /// <value>The container.</value>
+ public string? Container { get; set; }
- public MediaStreamProtocol SubProtocol { get; set; }
+ /// <summary>
+ /// Gets or sets the sub protocol.
+ /// </summary>
+ /// <value>The sub protocol.</value>
+ public MediaStreamProtocol SubProtocol { get; set; }
- public long StartPositionTicks { get; set; }
+ /// <summary>
+ /// Gets or sets the start position ticks.
+ /// </summary>
+ /// <value>The start position ticks.</value>
+ public long StartPositionTicks { get; set; }
- public int? SegmentLength { get; set; }
+ /// <summary>
+ /// Gets or sets the segment length.
+ /// </summary>
+ /// <value>The segment length.</value>
+ public int? SegmentLength { get; set; }
- public int? MinSegments { get; set; }
+ /// <summary>
+ /// Gets or sets the minimum segments count.
+ /// </summary>
+ /// <value>The minimum segments count.</value>
+ public int? MinSegments { get; set; }
- public bool BreakOnNonKeyFrames { 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 RequireAvc { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether the stream requires AVC.
+ /// </summary>
+ public bool RequireAvc { get; set; }
- public bool RequireNonAnamorphic { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether the stream requires AVC.
+ /// </summary>
+ public bool RequireNonAnamorphic { get; set; }
- public bool CopyTimestamps { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether timestamps should be copied.
+ /// </summary>
+ public bool CopyTimestamps { get; set; }
- public bool EnableMpegtsM2TsMode { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether timestamps should be copied.
+ /// </summary>
+ public bool EnableMpegtsM2TsMode { get; set; }
- public bool EnableSubtitlesInManifest { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether the subtitle manifest is enabled.
+ /// </summary>
+ 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; }
- public int? AudioStreamIndex { get; set; }
+ /// <summary>
+ /// Gets or sets the audio stream index.
+ /// </summary>
+ /// <value>The audio stream index.</value>
+ public int? AudioStreamIndex { get; set; }
- public int? SubtitleStreamIndex { get; set; }
+ /// <summary>
+ /// Gets or sets the video stream index.
+ /// </summary>
+ /// <value>The subtitle stream index.</value>
+ public int? SubtitleStreamIndex { get; set; }
- public int? TranscodingMaxAudioChannels { 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? GlobalMaxAudioChannels { 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? AudioBitrate { get; set; }
+ /// <summary>
+ /// Gets or sets the audio bitrate.
+ /// </summary>
+ /// <value>The audio bitrate.</value>
+ public int? AudioBitrate { get; set; }
- public int? AudioSampleRate { get; set; }
+ /// <summary>
+ /// Gets or sets the audio sample rate.
+ /// </summary>
+ /// <value>The audio sample rate.</value>
+ public int? AudioSampleRate { get; set; }
- public int? VideoBitrate { get; set; }
+ /// <summary>
+ /// Gets or sets the video bitrate.
+ /// </summary>
+ /// <value>The video bitrate.</value>
+ public int? VideoBitrate { get; set; }
- public int? MaxWidth { get; set; }
+ /// <summary>
+ /// Gets or sets the maximum output width.
+ /// </summary>
+ /// <value>The output width.</value>
+ public int? MaxWidth { get; set; }
- public int? MaxHeight { get; set; }
+ /// <summary>
+ /// Gets or sets the maximum output height.
+ /// </summary>
+ /// <value>The maximum output height.</value>
+ public int? MaxHeight { get; set; }
- public float? MaxFramerate { get; set; }
+ /// <summary>
+ /// Gets or sets the maximum framerate.
+ /// </summary>
+ /// <value>The maximum framerate.</value>
+ public float? MaxFramerate { get; set; }
- public required DeviceProfile DeviceProfile { get; set; }
+ /// <summary>
+ /// Gets or sets the device profile.
+ /// </summary>
+ /// <value>The device profile.</value>
+ public required DeviceProfile DeviceProfile { get; set; }
- public string? DeviceProfileId { get; set; }
+ /// <summary>
+ /// Gets or sets the device profile id.
+ /// </summary>
+ /// <value>The device profile id.</value>
+ public string? DeviceProfileId { get; set; }
- public string? DeviceId { get; set; }
+ /// <summary>
+ /// Gets or sets the device id.
+ /// </summary>
+ /// <value>The device id.</value>
+ public string? DeviceId { get; set; }
- public long? RunTimeTicks { get; set; }
+ /// <summary>
+ /// Gets or sets the runtime ticks.
+ /// </summary>
+ /// <value>The runtime ticks.</value>
+ public long? RunTimeTicks { get; set; }
- public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
+ /// <summary>
+ /// Gets or sets the transcode seek info.
+ /// </summary>
+ /// <value>The transcode seek info.</value>
+ public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
- public bool EstimateContentLength { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether content length should be estimated.
+ /// </summary>
+ public bool EstimateContentLength { get; set; }
- public MediaSourceInfo? MediaSource { get; set; }
+ /// <summary>
+ /// Gets or sets the media source info.
+ /// </summary>
+ /// <value>The media source info.</value>
+ 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; }
- public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
+ /// <summary>
+ /// Gets or sets the subtitle delivery method.
+ /// </summary>
+ /// <value>The subtitle delivery method.</value>
+ public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
- public string? SubtitleFormat { get; set; }
+ /// <summary>
+ /// Gets or sets the subtitle format.
+ /// </summary>
+ /// <value>The subtitle format.</value>
+ public string? SubtitleFormat { get; set; }
- public string? PlaySessionId { get; set; }
+ /// <summary>
+ /// Gets or sets the play session id.
+ /// </summary>
+ /// <value>The play session id.</value>
+ public string? PlaySessionId { get; set; }
- public TranscodeReason TranscodeReasons { get; set; }
+ /// <summary>
+ /// Gets or sets the transcode reasons.
+ /// </summary>
+ /// <value>The transcode reasons.</value>
+ public TranscodeReason TranscodeReasons { get; set; }
- public Dictionary<string, string> StreamOptions { get; private set; }
+ /// <summary>
+ /// Gets the stream options.
+ /// </summary>
+ /// <value>The stream options.</value>
+ public Dictionary<string, string> StreamOptions { get; private set; }
- public string? MediaSourceId => MediaSource?.Id;
+ /// <summary>
+ /// Gets the media source id.
+ /// </summary>
+ /// <value>The media source id.</value>
+ public string? MediaSourceId => MediaSource?.Id;
- public bool EnableAudioVbrEncoding { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether audio VBR encoding is enabled.
+ /// </summary>
+ public bool EnableAudioVbrEncoding { get; set; }
- public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay)
- && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay;
+ /// <summary>
+ /// Gets a value indicating whether the stream is direct.
+ /// </summary>
+ public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay)
+ && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay;
- /// <summary>
- /// Gets the audio stream that will be used.
- /// </summary>
- public MediaStream? TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex);
+ /// <summary>
+ /// Gets the audio stream that will be used in the output stream.
+ /// </summary>
+ /// <value>The audio stream.</value>
+ public MediaStream? TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex);
- /// <summary>
- /// Gets the video stream that will be used.
- /// </summary>
- public MediaStream? TargetVideoStream => MediaSource?.VideoStream;
+ /// <summary>
+ /// Gets the video stream that will be used in the output stream.
+ /// </summary>
+ /// <value>The video stream.</value>
+ public MediaStream? TargetVideoStream => MediaSource?.VideoStream;
- /// <summary>
- /// Gets the audio sample rate that will be in the output stream.
- /// </summary>
- public int? TargetAudioSampleRate
+ /// <summary>
+ /// Gets the audio sample rate that will be in the output stream.
+ /// </summary>
+ /// <value>The target audio sample rate.</value>
+ public int? TargetAudioSampleRate
+ {
+ get
{
- get
- {
- var stream = TargetAudioStream;
- return AudioSampleRate.HasValue && !IsDirectStream
- ? AudioSampleRate
- : stream?.SampleRate;
- }
+ var stream = TargetAudioStream;
+ return AudioSampleRate.HasValue && !IsDirectStream
+ ? AudioSampleRate
+ : stream?.SampleRate;
}
+ }
- /// <summary>
- /// Gets the audio sample rate that will be in the output stream.
- /// </summary>
- public int? TargetAudioBitDepth
+ /// <summary>
+ /// Gets the audio bit depth that will be in the output stream.
+ /// </summary>
+ /// <value>The target bit depth.</value>
+ public int? TargetAudioBitDepth
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- if (IsDirectStream)
- {
- return TargetAudioStream?.BitDepth;
- }
-
- var targetAudioCodecs = TargetAudioCodec;
- var audioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
- if (!string.IsNullOrEmpty(audioCodec))
- {
- return GetTargetAudioBitDepth(audioCodec);
- }
-
return TargetAudioStream?.BitDepth;
}
- }
- /// <summary>
- /// Gets the audio sample rate that will be in the output stream.
- /// </summary>
- public int? TargetVideoBitDepth
- {
- get
+ var targetAudioCodecs = TargetAudioCodec;
+ var audioCodec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0];
+ if (!string.IsNullOrEmpty(audioCodec))
{
- if (IsDirectStream)
- {
- return TargetVideoStream?.BitDepth;
- }
-
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
- {
- return GetTargetVideoBitDepth(videoCodec);
- }
-
- return TargetVideoStream?.BitDepth;
+ return GetTargetAudioBitDepth(audioCodec);
}
+
+ return TargetAudioStream?.BitDepth;
}
+ }
- /// <summary>
- /// Gets the target reference frames.
- /// </summary>
- /// <value>The target reference frames.</value>
- public int? TargetRefFrames
+ /// <summary>
+ /// Gets the video bit depth that will be in the output stream.
+ /// </summary>
+ /// <value>The target video bit depth.</value>
+ public int? TargetVideoBitDepth
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- if (IsDirectStream)
- {
- return TargetVideoStream?.RefFrames;
- }
-
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
- {
- return GetTargetRefFrames(videoCodec);
- }
+ return TargetVideoStream?.BitDepth;
+ }
- return TargetVideoStream?.RefFrames;
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
+ {
+ return GetTargetVideoBitDepth(videoCodec);
}
+
+ return TargetVideoStream?.BitDepth;
}
+ }
- /// <summary>
- /// Gets the audio sample rate that will be in the output stream.
- /// </summary>
- public float? TargetFramerate
+ /// <summary>
+ /// Gets the target reference frames that will be in the output stream.
+ /// </summary>
+ /// <value>The target reference frames.</value>
+ public int? TargetRefFrames
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- var stream = TargetVideoStream;
- return MaxFramerate.HasValue && !IsDirectStream
- ? MaxFramerate
- : stream?.ReferenceFrameRate;
+ return TargetVideoStream?.RefFrames;
}
- }
- /// <summary>
- /// Gets the audio sample rate that will be in the output stream.
- /// </summary>
- public double? TargetVideoLevel
- {
- get
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
{
- if (IsDirectStream)
- {
- return TargetVideoStream?.Level;
- }
+ return GetTargetRefFrames(videoCodec);
+ }
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
- {
- return GetTargetVideoLevel(videoCodec);
- }
+ return TargetVideoStream?.RefFrames;
+ }
+ }
- return TargetVideoStream?.Level;
- }
+ /// <summary>
+ /// Gets the target framerate that will be in the output stream.
+ /// </summary>
+ /// <value>The target framerate.</value>
+ public float? TargetFramerate
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+ return MaxFramerate.HasValue && !IsDirectStream
+ ? MaxFramerate
+ : stream?.ReferenceFrameRate;
}
+ }
- /// <summary>
- /// Gets the audio sample rate that will be in the output stream.
- /// </summary>
- public int? TargetPacketLength
+ /// <summary>
+ /// Gets the target video level that will be in the output stream.
+ /// </summary>
+ /// <value>The target video level.</value>
+ public double? TargetVideoLevel
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- var stream = TargetVideoStream;
- return !IsDirectStream
- ? null
- : stream?.PacketLength;
+ return TargetVideoStream?.Level;
}
- }
- /// <summary>
- /// Gets the audio sample rate that will be in the output stream.
- /// </summary>
- public string? TargetVideoProfile
- {
- get
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
{
- if (IsDirectStream)
- {
- return TargetVideoStream?.Profile;
- }
+ return GetTargetVideoLevel(videoCodec);
+ }
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
- {
- return GetOption(videoCodec, "profile");
- }
+ return TargetVideoStream?.Level;
+ }
+ }
- return TargetVideoStream?.Profile;
- }
+ /// <summary>
+ /// Gets the target packet length that will be in the output stream.
+ /// </summary>
+ /// <value>The target packet length.</value>
+ public int? TargetPacketLength
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+ return !IsDirectStream
+ ? null
+ : stream?.PacketLength;
}
+ }
- /// <summary>
- /// Gets the target video range type that will be in the output stream.
- /// </summary>
- public VideoRangeType TargetVideoRangeType
+ /// <summary>
+ /// Gets the target video profile that will be in the output stream.
+ /// </summary>
+ /// <value>The target video profile.</value>
+ public string? TargetVideoProfile
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- if (IsDirectStream)
- {
- return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
- }
-
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec)
- && Enum.TryParse(GetOption(videoCodec, "rangetype"), true, out VideoRangeType videoRangeType))
- {
- return videoRangeType;
- }
+ return TargetVideoStream?.Profile;
+ }
- return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
+ {
+ return GetOption(videoCodec, "profile");
}
+
+ return TargetVideoStream?.Profile;
}
+ }
- /// <summary>
- /// Gets the target video codec tag.
- /// </summary>
- /// <value>The target video codec tag.</value>
- public string? TargetVideoCodecTag
+ /// <summary>
+ /// Gets the target video range type that will be in the output stream.
+ /// </summary>
+ /// <value>The video range type.</value>
+ public VideoRangeType TargetVideoRangeType
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- var stream = TargetVideoStream;
- return !IsDirectStream
- ? null
- : stream?.CodecTag;
+ return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
}
- }
- /// <summary>
- /// Gets the audio bitrate that will be in the output stream.
- /// </summary>
- public int? TargetAudioBitrate
- {
- get
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec)
+ && Enum.TryParse(GetOption(videoCodec, "rangetype"), true, out VideoRangeType videoRangeType))
{
- var stream = TargetAudioStream;
- return AudioBitrate.HasValue && !IsDirectStream
- ? AudioBitrate
- : stream?.BitRate;
+ return videoRangeType;
}
+
+ return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
}
+ }
- /// <summary>
- /// Gets the audio channels that will be in the output stream.
- /// </summary>
- public int? TargetAudioChannels
+ /// <summary>
+ /// Gets the target video codec tag.
+ /// </summary>
+ /// <value>The video codec tag.</value>
+ public string? TargetVideoCodecTag
+ {
+ get
{
- get
- {
- if (IsDirectStream)
- {
- return TargetAudioStream?.Channels;
- }
+ var stream = TargetVideoStream;
+ return !IsDirectStream
+ ? null
+ : stream?.CodecTag;
+ }
+ }
- var targetAudioCodecs = TargetAudioCodec;
- var codec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0];
- if (!string.IsNullOrEmpty(codec))
- {
- return GetTargetRefFrames(codec);
- }
+ /// <summary>
+ /// Gets the audio bitrate that will be in the output stream.
+ /// </summary>
+ /// <value>The audio bitrate.</value>
+ public int? TargetAudioBitrate
+ {
+ get
+ {
+ var stream = TargetAudioStream;
+ return AudioBitrate.HasValue && !IsDirectStream
+ ? AudioBitrate
+ : stream?.BitRate;
+ }
+ }
+ /// <summary>
+ /// Gets the amount of audio channels that will be in the output stream.
+ /// </summary>
+ /// <value>The target audio channels.</value>
+ public int? TargetAudioChannels
+ {
+ get
+ {
+ if (IsDirectStream)
+ {
return TargetAudioStream?.Channels;
}
+
+ var targetAudioCodecs = TargetAudioCodec;
+ var codec = targetAudioCodecs.Count == 0 ? null : targetAudioCodecs[0];
+ if (!string.IsNullOrEmpty(codec))
+ {
+ return GetTargetRefFrames(codec);
+ }
+
+ return TargetAudioStream?.Channels;
}
+ }
- /// <summary>
- /// Gets the audio codec that will be in the output stream.
- /// </summary>
- public string[] TargetAudioCodec
+ /// <summary>
+ /// Gets the audio codec that will be in the output stream.
+ /// </summary>
+ /// <value>The audio codec.</value>
+ public IReadOnlyList<string> TargetAudioCodec
+ {
+ get
{
- get
- {
- var stream = TargetAudioStream;
+ var stream = TargetAudioStream;
- string? inputCodec = stream?.Codec;
+ string? inputCodec = stream?.Codec;
- if (IsDirectStream)
- {
- return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
- }
+ if (IsDirectStream)
+ {
+ 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];
}
-
- return AudioCodecs;
}
+
+ return AudioCodecs;
}
+ }
- 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
- {
- var stream = TargetVideoStream;
+ var stream = TargetVideoStream;
- string? inputCodec = stream?.Codec;
+ string? inputCodec = stream?.Codec;
- if (IsDirectStream)
- {
- return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
- }
+ if (IsDirectStream)
+ {
+ 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];
}
-
- return VideoCodecs;
}
+
+ return VideoCodecs;
}
+ }
- /// <summary>
- /// Gets the audio channels that will be in the output stream.
- /// </summary>
- public long? TargetSize
+ /// <summary>
+ /// Gets the target size of the output stream.
+ /// </summary>
+ /// <value>The target size.</value>
+ public long? TargetSize
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- if (IsDirectStream)
- {
- return MediaSource?.Size;
- }
-
- if (RunTimeTicks.HasValue)
- {
- int? totalBitrate = TargetTotalBitrate;
+ return MediaSource?.Size;
+ }
- double totalSeconds = RunTimeTicks.Value;
- // Convert to ms
- totalSeconds /= 10000;
- // Convert to seconds
- totalSeconds /= 1000;
+ if (RunTimeTicks.HasValue)
+ {
+ int? totalBitrate = TargetTotalBitrate;
- return totalBitrate.HasValue ?
- Convert.ToInt64(totalBitrate.Value * totalSeconds) :
- null;
- }
+ double totalSeconds = RunTimeTicks.Value;
+ // Convert to ms
+ totalSeconds /= 10000;
+ // Convert to seconds
+ totalSeconds /= 1000;
- return null;
+ return totalBitrate.HasValue ?
+ Convert.ToInt64(totalBitrate.Value * totalSeconds) :
+ null;
}
+
+ return null;
}
+ }
- public int? TargetVideoBitrate
+ /// <summary>
+ /// Gets the target video bitrate of the output stream.
+ /// </summary>
+ /// <value>The video bitrate.</value>
+ public int? TargetVideoBitrate
+ {
+ get
{
- get
- {
- var stream = TargetVideoStream;
+ var stream = TargetVideoStream;
- return VideoBitrate.HasValue && !IsDirectStream
- ? VideoBitrate
- : stream?.BitRate;
- }
+ return VideoBitrate.HasValue && !IsDirectStream
+ ? VideoBitrate
+ : stream?.BitRate;
}
+ }
- public TransportStreamTimestamp TargetTimestamp
+ /// <summary>
+ /// Gets the target timestamp of the output stream.
+ /// </summary>
+ /// <value>The target timestamp.</value>
+ public TransportStreamTimestamp TargetTimestamp
+ {
+ get
{
- get
- {
- var defaultValue = string.Equals(Container, "m2ts", StringComparison.OrdinalIgnoreCase)
- ? TransportStreamTimestamp.Valid
- : TransportStreamTimestamp.None;
+ var defaultValue = string.Equals(Container, "m2ts", StringComparison.OrdinalIgnoreCase)
+ ? TransportStreamTimestamp.Valid
+ : TransportStreamTimestamp.None;
- return !IsDirectStream
- ? defaultValue
- : MediaSource is null ? defaultValue : MediaSource.Timestamp ?? TransportStreamTimestamp.None;
- }
+ return !IsDirectStream
+ ? defaultValue
+ : MediaSource is null ? defaultValue : MediaSource.Timestamp ?? TransportStreamTimestamp.None;
}
+ }
- public int? TargetTotalBitrate => (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0);
+ /// <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 bool? IsTargetAnamorphic
+ /// <summary>
+ /// Gets a value indicating whether the output stream is anamorphic.
+ /// </summary>
+ public bool? IsTargetAnamorphic
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- if (IsDirectStream)
- {
- return TargetVideoStream?.IsAnamorphic;
- }
-
- return false;
+ return TargetVideoStream?.IsAnamorphic;
}
+
+ return false;
}
+ }
- public bool? IsTargetInterlaced
+ /// <summary>
+ /// Gets a value indicating whether the output stream is interlaced.
+ /// </summary>
+ public bool? IsTargetInterlaced
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- if (IsDirectStream)
- {
- return TargetVideoStream?.IsInterlaced;
- }
-
- var targetVideoCodecs = TargetVideoCodec;
- var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0];
- if (!string.IsNullOrEmpty(videoCodec))
- {
- if (string.Equals(GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- }
-
return TargetVideoStream?.IsInterlaced;
}
- }
- public bool? IsTargetAVC
- {
- get
+ var targetVideoCodecs = TargetVideoCodec;
+ var videoCodec = targetVideoCodecs.Count == 0 ? null : targetVideoCodecs[0];
+ if (!string.IsNullOrEmpty(videoCodec))
{
- if (IsDirectStream)
+ if (string.Equals(GetOption(videoCodec, "deinterlace"), "true", StringComparison.OrdinalIgnoreCase))
{
- return TargetVideoStream?.IsAVC;
+ return false;
}
-
- return true;
}
+
+ return TargetVideoStream?.IsInterlaced;
}
+ }
- public int? TargetWidth
+ /// <summary>
+ /// Gets a value indicating whether the output stream is AVC.
+ /// </summary>
+ public bool? IsTargetAVC
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- var videoStream = TargetVideoStream;
-
- if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue)
- {
- ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
-
- size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
-
- return size.Width;
- }
-
- return MaxWidth;
+ return TargetVideoStream?.IsAVC;
}
+
+ return true;
}
+ }
- public int? TargetHeight
+ /// <summary>
+ /// Gets the target width of the output stream.
+ /// </summary>
+ /// <value>The target width.</value>
+ public int? TargetWidth
+ {
+ get
{
- get
- {
- var videoStream = TargetVideoStream;
-
- if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue)
- {
- ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
+ var videoStream = TargetVideoStream;
- size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
+ if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue)
+ {
+ ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
- return size.Height;
- }
+ size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
- return MaxHeight;
+ return size.Width;
}
+
+ return MaxWidth;
}
+ }
- public int? TargetVideoStreamCount
+ /// <summary>
+ /// Gets the target height of the output stream.
+ /// </summary>
+ /// <value>The target height.</value>
+ public int? TargetHeight
+ {
+ get
{
- get
+ var videoStream = TargetVideoStream;
+
+ if (videoStream is not null && videoStream.Width.HasValue && videoStream.Height.HasValue)
{
- if (IsDirectStream)
- {
- return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
- }
+ ImageDimensions size = new ImageDimensions(videoStream.Width.Value, videoStream.Height.Value);
- return GetMediaStreamCount(MediaStreamType.Video, 1);
+ size = DrawingUtils.Resize(size, 0, 0, MaxWidth ?? 0, MaxHeight ?? 0);
+
+ return size.Height;
}
+
+ return MaxHeight;
}
+ }
- public int? TargetAudioStreamCount
+ /// <summary>
+ /// Gets the target video stream count of the output stream.
+ /// </summary>
+ /// <value>The target video stream count.</value>
+ public int? TargetVideoStreamCount
+ {
+ get
{
- get
+ if (IsDirectStream)
{
- if (IsDirectStream)
- {
- return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
- }
-
- return GetMediaStreamCount(MediaStreamType.Audio, 1);
+ return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
}
+
+ return GetMediaStreamCount(MediaStreamType.Video, 1);
}
+ }
- public void SetOption(string? qualifier, string name, string value)
+ /// <summary>
+ /// Gets the target audio stream count of the output stream.
+ /// </summary>
+ /// <value>The target audio stream count.</value>
+ public int? TargetAudioStreamCount
+ {
+ get
{
- if (string.IsNullOrEmpty(qualifier))
- {
- SetOption(name, value);
- }
- else
+ if (IsDirectStream)
{
- SetOption(qualifier + "-" + name, value);
+ return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
}
+
+ return GetMediaStreamCount(MediaStreamType.Audio, 1);
}
+ }
- public void SetOption(string name, string value)
+ /// <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)
+ {
+ if (string.IsNullOrEmpty(qualifier))
{
- StreamOptions[name] = value;
+ SetOption(name, value);
}
-
- public string? GetOption(string? qualifier, string name)
+ else
{
- var value = GetOption(qualifier + "-" + name);
+ SetOption(qualifier + "-" + name, value);
+ }
+ }
- if (string.IsNullOrEmpty(value))
- {
- value = GetOption(name);
- }
+ /// <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)
+ {
+ StreamOptions[name] = value;
+ }
- return 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)
+ {
+ var value = GetOption(qualifier + "-" + name);
- public string? GetOption(string name)
+ if (string.IsNullOrEmpty(value))
{
- if (StreamOptions.TryGetValue(name, out var value))
- {
- return value;
- }
-
- return null;
+ value = GetOption(name);
}
- public string ToUrl(string baseUrl, string? accessToken)
+ return value;
+ }
+
+ /// <summary>
+ /// Gets a stream option.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <returns>The value.</returns>
+ public string? GetOption(string name)
+ {
+ if (StreamOptions.TryGetValue(name, out var value))
{
- ArgumentException.ThrowIfNullOrEmpty(baseUrl);
+ return value;
+ }
- var list = new List<string>();
- foreach (NameValuePair pair in BuildParams(this, accessToken))
- {
- if (string.IsNullOrEmpty(pair.Value))
- {
- continue;
- }
+ return null;
+ }
- // Try to keep the url clean by omitting defaults
- if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase)
- && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
+ /// <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)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(baseUrl);
- if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase)
- && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
+ List<string> list = [];
+ foreach (NameValuePair pair in BuildParams(this, accessToken))
+ {
+ if (string.IsNullOrEmpty(pair.Value))
+ {
+ continue;
+ }
- if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase)
- && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
+ // Try to keep the url clean by omitting defaults
+ if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
- var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal);
+ if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
- list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
+ if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
}
- string queryString = string.Join('&', list);
+ var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal);
- return GetUrl(baseUrl, queryString);
+ list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
}
- private string GetUrl(string baseUrl, string queryString)
- {
- ArgumentException.ThrowIfNullOrEmpty(baseUrl);
+ string queryString = string.Join('&', list);
- string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
+ return GetUrl(baseUrl, queryString);
+ }
- baseUrl = baseUrl.TrimEnd('/');
+ private string GetUrl(string baseUrl, string queryString)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(baseUrl);
- if (MediaType == DlnaProfileType.Audio)
- {
- if (SubProtocol == MediaStreamProtocol.hls)
- {
- return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
- }
+ string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
- return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
- }
+ baseUrl = baseUrl.TrimEnd('/');
+ if (MediaType == DlnaProfileType.Audio)
+ {
if (SubProtocol == MediaStreamProtocol.hls)
{
- return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
+ return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
}
- return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
+ return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
}
- private static IEnumerable<NameValuePair> BuildParams(StreamInfo item, string? accessToken)
+ if (SubProtocol == MediaStreamProtocol.hls)
{
- var list = new List<NameValuePair>();
+ return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
+ }
- string audioCodecs = item.AudioCodecs.Length == 0 ?
- string.Empty :
- string.Join(',', item.AudioCodecs);
+ return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
+ }
- string videoCodecs = item.VideoCodecs.Length == 0 ?
- string.Empty :
- string.Join(',', item.VideoCodecs);
+ private static List<NameValuePair> BuildParams(StreamInfo item, string? accessToken)
+ {
+ List<NameValuePair> list = [];
+
+ string audioCodecs = item.AudioCodecs.Count == 0 ?
+ string.Empty :
+ string.Join(',', item.AudioCodecs);
+
+ string videoCodecs = item.VideoCodecs.Count == 0 ?
+ string.Empty :
+ string.Join(',', item.VideoCodecs);
+
+ list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
+ list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
+ list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
+ list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ list.Add(new NameValuePair("VideoCodec", videoCodecs));
+ list.Add(new NameValuePair("AudioCodec", audioCodecs));
+ list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+
+ list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+
+ long startPositionTicks = item.StartPositionTicks;
+
+ if (item.SubProtocol == MediaStreamProtocol.hls)
+ {
+ list.Add(new NameValuePair("StartTimeTicks", string.Empty));
+ }
+ else
+ {
+ list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
+ }
- list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
- list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
- list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
- list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- list.Add(new NameValuePair("VideoCodec", videoCodecs));
- list.Add(new NameValuePair("AudioCodec", audioCodecs));
- list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
+ list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
- list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ string? liveStreamId = item.MediaSource?.LiveStreamId;
+ list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
- long startPositionTicks = item.StartPositionTicks;
+ list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
- if (item.SubProtocol == MediaStreamProtocol.hls)
- {
- list.Add(new NameValuePair("StartTimeTicks", string.Empty));
- }
- else
+ if (!item.IsDirectStream)
+ {
+ if (item.RequireNonAnamorphic)
{
- list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
+ list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
}
- list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
- list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
-
- string? liveStreamId = item.MediaSource?.LiveStreamId;
- list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
-
- list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
+ list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- if (!item.IsDirectStream)
+ if (item.EnableSubtitlesInManifest)
{
- if (item.RequireNonAnamorphic)
- {
- list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- }
-
- list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
-
- if (item.EnableSubtitlesInManifest)
- {
- list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- }
-
- if (item.EnableMpegtsM2TsMode)
- {
- list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- }
-
- if (item.EstimateContentLength)
- {
- list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- }
+ list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ }
- if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
- {
- list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
- }
+ if (item.EnableMpegtsM2TsMode)
+ {
+ list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ }
- if (item.CopyTimestamps)
- {
- list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- }
+ if (item.EstimateContentLength)
+ {
+ list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ }
- list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
+ {
+ list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
+ }
- list.Add(new NameValuePair("EnableAudioVbrEncoding", item.EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ if (item.CopyTimestamps)
+ {
+ list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
}
- list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty));
+ list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
- string subtitleCodecs = item.SubtitleCodecs.Length == 0 ?
- string.Empty :
- string.Join(",", item.SubtitleCodecs);
+ list.Add(new NameValuePair("EnableAudioVbrEncoding", item.EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ }
- list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
+ list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty));
- if (item.SubProtocol == MediaStreamProtocol.hls)
- {
- list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
+ string subtitleCodecs = item.SubtitleCodecs.Count == 0 ?
+ string.Empty :
+ string.Join(",", item.SubtitleCodecs);
- if (item.SegmentLength.HasValue)
- {
- list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
- }
+ list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
- if (item.MinSegments.HasValue)
- {
- list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
- }
+ if (item.SubProtocol == MediaStreamProtocol.hls)
+ {
+ list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
- list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
+ if (item.SegmentLength.HasValue)
+ {
+ list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
}
- foreach (var pair in item.StreamOptions)
+ if (item.MinSegments.HasValue)
{
- if (string.IsNullOrEmpty(pair.Value))
- {
- continue;
- }
-
- // strip spaces to avoid having to encode h264 profile names
- list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)));
+ list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
}
- if (!item.IsDirectStream)
+ list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
+ }
+
+ foreach (var pair in item.StreamOptions)
+ {
+ if (string.IsNullOrEmpty(pair.Value))
{
- list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString()));
+ continue;
}
- return list;
+ // strip spaces to avoid having to encode h264 profile names
+ list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)));
}
- public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string? accessToken)
+ if (!item.IsDirectStream)
{
- return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken);
+ list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString()));
}
- public IEnumerable<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string? accessToken)
+ 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)
+ {
+ 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)
+ {
+ 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
- long startPositionTicks = SubProtocol == MediaStreamProtocol.hls
- ? 0
- : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
+ // HLS will preserve timestamps so we can just grab the full subtitle stream
+ long startPositionTicks = SubProtocol == MediaStreamProtocol.hls
+ ? 0
+ : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
- // First add the selected track
- if (SubtitleStreamIndex.HasValue)
+ // First add the selected track
+ if (SubtitleStreamIndex.HasValue)
+ {
+ foreach (var stream in MediaSource.MediaStreams)
{
- foreach (var stream in MediaSource.MediaStreams)
+ if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
{
- if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
- {
- AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
- }
+ AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
}
}
+ }
- if (!includeSelectedTrackOnly)
+ if (!includeSelectedTrackOnly)
+ {
+ foreach (var stream in MediaSource.MediaStreams)
{
- foreach (var stream in MediaSource.MediaStreams)
+ if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
{
- if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
- {
- AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
- }
+ AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
}
}
-
- return list;
}
- private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string? accessToken, long startPositionTicks)
+ return list;
+ }
+
+ private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string? accessToken, long startPositionTicks)
+ {
+ if (enableAllProfiles)
{
- if (enableAllProfiles)
+ foreach (var profile in DeviceProfile.SubtitleProfiles)
{
- foreach (var profile in DeviceProfile.SubtitleProfiles)
- {
- var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport);
- if (info is not null)
- {
- list.Add(info);
- }
- }
- }
- else
- {
- var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
+ var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport);
if (info is not null)
{
list.Add(info);
}
}
}
-
- private SubtitleStreamInfo? GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string? accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
+ else
{
- if (MediaSource is null)
+ var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport);
+ if (info is not null)
{
- return null;
+ list.Add(info);
}
+ }
+ }
- var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
- var info = new SubtitleStreamInfo
- {
- IsForced = stream.IsForced,
- Language = stream.Language,
- Name = stream.Language ?? "Unknown",
- Format = subtitleProfile.Format,
- Index = stream.Index,
- DeliveryMethod = subtitleProfile.Method,
- DisplayTitle = stream.DisplayTitle
- };
+ private SubtitleStreamInfo? GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string? accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport)
+ {
+ if (MediaSource is null)
+ {
+ return null;
+ }
- if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
+ var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol);
+ var info = new SubtitleStreamInfo
+ {
+ IsForced = stream.IsForced,
+ Language = stream.Language,
+ Name = stream.Language ?? "Unknown",
+ Format = subtitleProfile.Format,
+ Index = stream.Index,
+ DeliveryMethod = subtitleProfile.Method,
+ DisplayTitle = stream.DisplayTitle
+ };
+
+ if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
+ {
+ if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
{
- if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
+ info.Url = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
+ baseUrl,
+ ItemId,
+ MediaSourceId,
+ stream.Index.ToString(CultureInfo.InvariantCulture),
+ startPositionTicks.ToString(CultureInfo.InvariantCulture),
+ subtitleProfile.Format);
+
+ if (!string.IsNullOrEmpty(accessToken))
{
- info.Url = string.Format(
- CultureInfo.InvariantCulture,
- "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
- baseUrl,
- ItemId,
- MediaSourceId,
- stream.Index.ToString(CultureInfo.InvariantCulture),
- startPositionTicks.ToString(CultureInfo.InvariantCulture),
- subtitleProfile.Format);
-
- if (!string.IsNullOrEmpty(accessToken))
- {
- info.Url += "?api_key=" + accessToken;
- }
-
- info.IsExternalUrl = false;
+ info.Url += "?api_key=" + accessToken;
}
- else
- {
- info.Url = stream.Path;
- info.IsExternalUrl = true;
- }
- }
-
- return info;
- }
- public int? GetTargetVideoBitDepth(string? codec)
- {
- var value = GetOption(codec, "videobitdepth");
-
- if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
+ info.IsExternalUrl = false;
+ }
+ else
{
- return result;
+ info.Url = stream.Path;
+ info.IsExternalUrl = true;
}
-
- return null;
}
- public int? GetTargetAudioBitDepth(string? codec)
- {
- var value = GetOption(codec, "audiobitdepth");
+ return info;
+ }
- if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
+ /// <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)
+ {
+ var value = GetOption(codec, "videobitdepth");
- return null;
+ if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
}
- public double? GetTargetVideoLevel(string? codec)
- {
- var value = GetOption(codec, "level");
+ return null;
+ }
- if (double.TryParse(value, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
+ /// <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)
+ {
+ var value = GetOption(codec, "audiobitdepth");
- return null;
+ if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
}
- public int? GetTargetRefFrames(string? codec)
- {
- var value = GetOption(codec, "maxrefframes");
+ return null;
+ }
- if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
+ /// <summary>
+ /// Gets the target video level.
+ /// </summary>
+ /// <param name="codec">The codec.</param>
+ /// <returns>The target video level.</returns>
+ public double? GetTargetVideoLevel(string? codec)
+ {
+ var value = GetOption(codec, "level");
- return null;
+ if (double.TryParse(value, CultureInfo.InvariantCulture, out var result))
+ {
+ return result;
}
- public int? GetTargetAudioChannels(string? codec)
+ 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)
+ {
+ var value = GetOption(codec, "maxrefframes");
+
+ if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
{
- var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
+ return result;
+ }
- var value = GetOption(codec, "audiochannels");
- if (string.IsNullOrEmpty(value))
- {
- return defaultValue;
- }
+ return null;
+ }
- if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
- {
- return Math.Min(result, defaultValue ?? result);
- }
+ /// <summary>
+ /// Gets the target audio channels.
+ /// </summary>
+ /// <param name="codec">The codec.</param>
+ /// <returns>The target audio channels.</returns>
+ public int? GetTargetAudioChannels(string? codec)
+ {
+ var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels;
+ var value = GetOption(codec, "audiochannels");
+ if (string.IsNullOrEmpty(value))
+ {
return defaultValue;
}
- private int? GetMediaStreamCount(MediaStreamType type, int limit)
+ if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
- var count = MediaSource?.GetStreamCount(type);
+ return Math.Min(result, defaultValue ?? result);
+ }
- if (count.HasValue)
- {
- count = Math.Min(count.Value, limit);
- }
+ 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)
+ {
+ var count = MediaSource?.GetStreamCount(type);
- return count;
+ if (count.HasValue)
+ {
+ count = Math.Min(count.Value, limit);
}
+
+ return count;
}
}
diff --git a/MediaBrowser.Model/Dlna/SubtitleProfile.cs b/MediaBrowser.Model/Dlna/SubtitleProfile.cs
index 9ebde25ff..1879f2dd2 100644
--- a/MediaBrowser.Model/Dlna/SubtitleProfile.cs
+++ b/MediaBrowser.Model/Dlna/SubtitleProfile.cs
@@ -1,48 +1,62 @@
#nullable disable
-#pragma warning disable CS1591
-using System;
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")]
+ public string Format { get; set; }
+
+ /// <summary>
+ /// Gets or sets the delivery method.
+ /// </summary>
+ [XmlAttribute("method")]
+ public SubtitleDeliveryMethod Method { get; set; }
+
+ /// <summary>
+ /// Gets or sets the DIDL mode.
+ /// </summary>
+ [XmlAttribute("didlMode")]
+ public string DidlMode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the language.
+ /// </summary>
+ [XmlAttribute("language")]
+ public string Language { get; set; }
+
+ /// <summary>
+ /// Gets or sets the container.
+ /// </summary>
+ [XmlAttribute("container")]
+ public string Container { get; set; }
+
+ /// <summary>
+ /// Checks if a language is supported.
+ /// </summary>
+ /// <param name="subLanguage">The language to check for support.</param>
+ /// <returns><c>true</c> if supported.</returns>
+ public bool SupportsLanguage(string subLanguage)
{
- [XmlAttribute("format")]
- public string Format { get; set; }
-
- [XmlAttribute("method")]
- public SubtitleDeliveryMethod Method { get; set; }
-
- [XmlAttribute("didlMode")]
- public string DidlMode { get; set; }
-
- [XmlAttribute("language")]
- public string Language { get; set; }
-
- [XmlAttribute("container")]
- public string Container { get; set; }
-
- public string[] GetLanguages()
+ if (string.IsNullOrEmpty(Language))
{
- return ContainerProfile.SplitValue(Language);
+ return true;
}
- public bool SupportsLanguage(string subLanguage)
+ if (string.IsNullOrEmpty(subLanguage))
{
- if (string.IsNullOrEmpty(Language))
- {
- return true;
- }
-
- if (string.IsNullOrEmpty(subLanguage))
- {
- subLanguage = "und";
- }
-
- var languages = GetLanguages();
- return languages.Length == 0 || languages.Contains(subLanguage, StringComparison.OrdinalIgnoreCase);
+ subLanguage = "und";
}
+
+ return ContainerHelper.ContainsContainer(Language, subLanguage);
}
}
diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
index a556799de..5a9fa22ae 100644
--- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs
+++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
@@ -1,82 +1,130 @@
-#pragma warning disable CS1591
-
-using System;
using System.ComponentModel;
using System.Xml.Serialization;
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>();
- }
-
- [XmlAttribute("container")]
- public string Container { get; set; } = string.Empty;
-
- [XmlAttribute("type")]
- public DlnaProfileType Type { get; set; }
-
- [XmlAttribute("videoCodec")]
- public string VideoCodec { get; set; } = string.Empty;
-
- [XmlAttribute("audioCodec")]
- public string AudioCodec { get; set; } = string.Empty;
-
- [XmlAttribute("protocol")]
- public MediaStreamProtocol Protocol { get; set; } = MediaStreamProtocol.http;
-
- [DefaultValue(false)]
- [XmlAttribute("estimateContentLength")]
- public bool EstimateContentLength { get; set; }
-
- [DefaultValue(false)]
- [XmlAttribute("enableMpegtsM2TsMode")]
- public bool EnableMpegtsM2TsMode { get; set; }
-
- [DefaultValue(TranscodeSeekInfo.Auto)]
- [XmlAttribute("transcodeSeekInfo")]
- public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
-
- [DefaultValue(false)]
- [XmlAttribute("copyTimestamps")]
- public bool CopyTimestamps { get; set; }
-
- [DefaultValue(EncodingContext.Streaming)]
- [XmlAttribute("context")]
- public EncodingContext Context { get; set; }
-
- [DefaultValue(false)]
- [XmlAttribute("enableSubtitlesInManifest")]
- public bool EnableSubtitlesInManifest { get; set; }
-
- [XmlAttribute("maxAudioChannels")]
- public string? MaxAudioChannels { get; set; }
-
- [DefaultValue(0)]
- [XmlAttribute("minSegments")]
- public int MinSegments { get; set; }
-
- [DefaultValue(0)]
- [XmlAttribute("segmentLength")]
- public int SegmentLength { get; set; }
-
- [DefaultValue(false)]
- [XmlAttribute("breakOnNonKeyFrames")]
- public bool BreakOnNonKeyFrames { get; set; }
-
- public ProfileCondition[] Conditions { get; set; }
-
- [DefaultValue(true)]
- [XmlAttribute("enableAudioVbrEncoding")]
- public bool EnableAudioVbrEncoding { get; set; } = true;
-
- public string[] GetAudioCodecs()
- {
- return ContainerProfile.SplitValue(AudioCodec);
- }
+ Conditions = [];
}
+
+ /// <summary>
+ /// Gets or sets the container.
+ /// </summary>
+ [XmlAttribute("container")]
+ public string Container { get; set; } = string.Empty;
+
+ /// <summary>
+ /// Gets or sets the DLNA profile type.
+ /// </summary>
+ [XmlAttribute("type")]
+ public DlnaProfileType Type { get; set; }
+
+ /// <summary>
+ /// Gets or sets the video codec.
+ /// </summary>
+ [XmlAttribute("videoCodec")]
+ public string VideoCodec { get; set; } = string.Empty;
+
+ /// <summary>
+ /// Gets or sets the audio codec.
+ /// </summary>
+ [XmlAttribute("audioCodec")]
+ public string AudioCodec { get; set; } = string.Empty;
+
+ /// <summary>
+ /// Gets or sets the protocol.
+ /// </summary>
+ [XmlAttribute("protocol")]
+ public MediaStreamProtocol Protocol { get; set; } = MediaStreamProtocol.http;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the content length should be estimated.
+ /// </summary>
+ [DefaultValue(false)]
+ [XmlAttribute("estimateContentLength")]
+ public bool EstimateContentLength { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether M2TS mode is enabled.
+ /// </summary>
+ [DefaultValue(false)]
+ [XmlAttribute("enableMpegtsM2TsMode")]
+ public bool EnableMpegtsM2TsMode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the transcoding seek info mode.
+ /// </summary>
+ [DefaultValue(TranscodeSeekInfo.Auto)]
+ [XmlAttribute("transcodeSeekInfo")]
+ public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether timestamps should be copied.
+ /// </summary>
+ [DefaultValue(false)]
+ [XmlAttribute("copyTimestamps")]
+ public bool CopyTimestamps { get; set; }
+
+ /// <summary>
+ /// Gets or sets the encoding context.
+ /// </summary>
+ [DefaultValue(EncodingContext.Streaming)]
+ [XmlAttribute("context")]
+ public EncodingContext Context { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether subtitles are allowed in the manifest.
+ /// </summary>
+ [DefaultValue(false)]
+ [XmlAttribute("enableSubtitlesInManifest")]
+ public bool EnableSubtitlesInManifest { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum audio channels.
+ /// </summary>
+ [XmlAttribute("maxAudioChannels")]
+ public string? MaxAudioChannels { get; set; }
+
+ /// <summary>
+ /// Gets or sets the minimum amount of segments.
+ /// </summary>
+ [DefaultValue(0)]
+ [XmlAttribute("minSegments")]
+ public int MinSegments { get; set; }
+
+ /// <summary>
+ /// Gets or sets the segment length.
+ /// </summary>
+ [DefaultValue(0)]
+ [XmlAttribute("segmentLength")]
+ 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)]
+ [XmlAttribute("breakOnNonKeyFrames")]
+ public bool BreakOnNonKeyFrames { get; set; }
+
+ /// <summary>
+ /// Gets or sets the profile conditions.
+ /// </summary>
+ public ProfileCondition[] Conditions { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether variable bitrate encoding is supported.
+ /// </summary>
+ [DefaultValue(true)]
+ [XmlAttribute("enableAudioVbrEncoding")]
+ public bool EnableAudioVbrEncoding { get; set; } = true;
}
diff --git a/MediaBrowser.Model/Extensions/ContainerHelper.cs b/MediaBrowser.Model/Extensions/ContainerHelper.cs
new file mode 100644
index 000000000..4b75657ff
--- /dev/null
+++ b/MediaBrowser.Model/Extensions/ContainerHelper.cs
@@ -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 (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 = inputContainer.Split(',');
+ 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) ?? [];
+ }
+}
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
index 51ac558b8..80bb1a514 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -316,7 +316,7 @@ namespace MediaBrowser.Providers.MediaInfo
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
: audio.Genres;
}
diff --git a/tests/Jellyfin.Model.Tests/Dlna/ContainerHelperTests.cs b/tests/Jellyfin.Model.Tests/Dlna/ContainerHelperTests.cs
new file mode 100644
index 000000000..68f8d94c7
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Dlna/ContainerHelperTests.cs
@@ -0,0 +1,54 @@
+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));
+ }
+
+ [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()));
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs b/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs
deleted file mode 100644
index cca056c28..000000000
--- a/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs
+++ /dev/null
@@ -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));
- }
- }
-}
diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
index 7b4bb05ff..bd2143f25 100644
--- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
+++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
@@ -389,18 +389,23 @@ namespace Jellyfin.Model.Tests
if (playMethod == PlayMethod.DirectPlay)
{
// Check expected container
- var containers = ContainerProfile.SplitValue(mediaSource.Container);
+ var containers = mediaSource.Container.Split(',');
+ Assert.Contains(uri.Extension, containers);
// TODO: Test transcode too
- // Assert.Contains(uri.Extension, containers);
// Check expected video codec (1)
- Assert.Contains(targetVideoStream?.Codec, streamInfo.TargetVideoCodec);
- Assert.Single(streamInfo.TargetVideoCodec);
+ if (targetVideoStream?.Codec is not null)
+ {
+ Assert.Contains(targetVideoStream?.Codec, streamInfo.TargetVideoCodec);
+ Assert.Single(streamInfo.TargetVideoCodec);
+ }
- // Check expected audio codecs (1)
- Assert.Contains(targetAudioStream?.Codec, streamInfo.TargetAudioCodec);
- Assert.Single(streamInfo.TargetAudioCodec);
- // Assert.Single(val.AudioCodecs);
+ if (targetAudioStream?.Codec is not null)
+ {
+ // Check expected audio codecs (1)
+ Assert.Contains(targetAudioStream?.Codec, streamInfo.TargetAudioCodec);
+ Assert.Single(streamInfo.TargetAudioCodec);
+ }
if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal))
{