aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model
diff options
context:
space:
mode:
authorJPVenson <github@jpb.email>2024-10-08 09:34:34 +0000
committerJPVenson <github@jpb.email>2024-10-08 09:34:34 +0000
commitd3a3d9fce3b891eb0be274a0cdc45a989e557652 (patch)
treebd232ef477c259f1fafa204016f6efd4dcb8691f /MediaBrowser.Model
parentee1bdf4e222125ed7382165fd7e09599ca4bd4aa (diff)
parentaaf20592bb0bbdf4f0f0d99fed091758e68ae850 (diff)
Merge remote-tracking branch 'jellyfinorigin/master' into feature/EFUserData
Diffstat (limited to 'MediaBrowser.Model')
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs38
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs28
-rw-r--r--MediaBrowser.Model/Configuration/MediaPathInfo.cs2
-rw-r--r--MediaBrowser.Model/Configuration/MetadataPluginType.cs3
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs2
-rw-r--r--MediaBrowser.Model/Devices/DeviceInfo.cs119
-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/MediaOptions.cs5
-rw-r--r--MediaBrowser.Model/Dlna/ResolutionNormalizer.cs69
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs303
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs1680
-rw-r--r--MediaBrowser.Model/Dlna/SubtitleProfile.cs84
-rw-r--r--MediaBrowser.Model/Dlna/TranscodingProfile.cs196
-rw-r--r--MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs69
-rw-r--r--MediaBrowser.Model/Dto/DeviceInfoDto.cs83
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs7
-rw-r--r--MediaBrowser.Model/Dto/PlaylistDto.cs26
-rw-r--r--MediaBrowser.Model/Dto/SessionInfoDto.cs186
-rw-r--r--MediaBrowser.Model/Entities/DeinterlaceMethod.cs19
-rw-r--r--MediaBrowser.Model/Entities/EncoderPreset.cs64
-rw-r--r--MediaBrowser.Model/Entities/HardwareAccelerationType.cs49
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs33
-rw-r--r--MediaBrowser.Model/Entities/TonemappingAlgorithm.cs49
-rw-r--r--MediaBrowser.Model/Entities/TonemappingMode.cs34
-rw-r--r--MediaBrowser.Model/Entities/TonemappingRange.cs24
-rw-r--r--MediaBrowser.Model/Extensions/ContainerHelper.cs145
-rw-r--r--MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs32
-rw-r--r--MediaBrowser.Model/IO/IFileSystem.cs7
-rw-r--r--MediaBrowser.Model/LiveTv/TunerHostInfo.cs9
-rw-r--r--MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs3
-rw-r--r--MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs14
-rw-r--r--MediaBrowser.Model/Session/HardwareEncodingType.cs43
-rw-r--r--MediaBrowser.Model/Session/TranscodeReason.cs1
-rw-r--r--MediaBrowser.Model/Session/TranscodingInfo.cs78
37 files changed, 2633 insertions, 1302 deletions
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 4c5213d4e..2720c0bdf 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA1819 // XML serialization handles collections improperly, so we need to use arrays
+
#nullable disable
using MediaBrowser.Model.Entities;
@@ -30,9 +32,9 @@ public class EncodingOptions
EnableTonemapping = false;
EnableVppTonemapping = false;
EnableVideoToolboxTonemapping = false;
- TonemappingAlgorithm = "bt2390";
- TonemappingMode = "auto";
- TonemappingRange = "auto";
+ TonemappingAlgorithm = TonemappingAlgorithm.bt2390;
+ TonemappingMode = TonemappingMode.auto;
+ TonemappingRange = TonemappingRange.auto;
TonemappingDesat = 0;
TonemappingPeak = 100;
TonemappingParam = 0;
@@ -41,9 +43,11 @@ public class EncodingOptions
H264Crf = 23;
H265Crf = 28;
DeinterlaceDoubleRate = false;
- DeinterlaceMethod = "yadif";
+ DeinterlaceMethod = DeinterlaceMethod.yadif;
EnableDecodingColorDepth10Hevc = true;
EnableDecodingColorDepth10Vp9 = true;
+ EnableDecodingColorDepth10HevcRext = false;
+ EnableDecodingColorDepth12HevcRext = false;
// Enhanced Nvdec or system native decoder is required for DoVi to SDR tone-mapping.
EnableEnhancedNvdecDecoder = true;
PreferSystemNativeHwDecoder = true;
@@ -53,8 +57,8 @@ public class EncodingOptions
AllowHevcEncoding = false;
AllowAv1Encoding = false;
EnableSubtitleExtraction = true;
- AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = new[] { "mkv" };
- HardwareDecodingCodecs = new string[] { "h264", "vc1" };
+ AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = ["mkv"];
+ HardwareDecodingCodecs = ["h264", "vc1"];
}
/// <summary>
@@ -120,7 +124,7 @@ public class EncodingOptions
/// <summary>
/// Gets or sets the hardware acceleration type.
/// </summary>
- public string HardwareAccelerationType { get; set; }
+ public HardwareAccelerationType HardwareAccelerationType { get; set; }
/// <summary>
/// Gets or sets the FFmpeg path as set by the user via the UI.
@@ -160,17 +164,17 @@ public class EncodingOptions
/// <summary>
/// Gets or sets the tone-mapping algorithm.
/// </summary>
- public string TonemappingAlgorithm { get; set; }
+ public TonemappingAlgorithm TonemappingAlgorithm { get; set; }
/// <summary>
/// Gets or sets the tone-mapping mode.
/// </summary>
- public string TonemappingMode { get; set; }
+ public TonemappingMode TonemappingMode { get; set; }
/// <summary>
/// Gets or sets the tone-mapping range.
/// </summary>
- public string TonemappingRange { get; set; }
+ public TonemappingRange TonemappingRange { get; set; }
/// <summary>
/// Gets or sets the tone-mapping desaturation.
@@ -210,7 +214,7 @@ public class EncodingOptions
/// <summary>
/// Gets or sets the encoder preset.
/// </summary>
- public string EncoderPreset { get; set; }
+ public EncoderPreset? EncoderPreset { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the framerate is doubled when deinterlacing.
@@ -220,7 +224,7 @@ public class EncodingOptions
/// <summary>
/// Gets or sets the deinterlace method.
/// </summary>
- public string DeinterlaceMethod { get; set; }
+ public DeinterlaceMethod DeinterlaceMethod { get; set; }
/// <summary>
/// Gets or sets a value indicating whether 10bit HEVC decoding is enabled.
@@ -233,6 +237,16 @@ public class EncodingOptions
public bool EnableDecodingColorDepth10Vp9 { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether 8/10bit HEVC RExt decoding is enabled.
+ /// </summary>
+ public bool EnableDecodingColorDepth10HevcRext { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether 12bit HEVC RExt decoding is enabled.
+ /// </summary>
+ public bool EnableDecodingColorDepth12HevcRext { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating whether the enhanced NVDEC is enabled.
/// </summary>
public bool EnableEnhancedNvdecDecoder { get; set; }
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index b0f5c2a11..6054ba34e 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -2,15 +2,20 @@
using System;
using System.ComponentModel;
+using System.Linq;
namespace MediaBrowser.Model.Configuration
{
public class LibraryOptions
{
+ private static readonly string[] _defaultTagDelimiters = ["/", "|", ";", "\\"];
+
public LibraryOptions()
{
TypeOptions = Array.Empty<TypeOptions>();
DisabledSubtitleFetchers = Array.Empty<string>();
+ DisabledMediaSegmentProviders = Array.Empty<string>();
+ MediaSegmentProvideOrder = Array.Empty<string>();
SubtitleFetcherOrder = Array.Empty<string>();
DisabledLocalMetadataReaders = Array.Empty<string>();
DisabledLyricFetchers = Array.Empty<string>();
@@ -24,9 +29,15 @@ namespace MediaBrowser.Model.Configuration
EnablePhotos = true;
SaveSubtitlesWithMedia = true;
SaveLyricsWithMedia = false;
+ SaveTrickplayWithMedia = false;
PathInfos = Array.Empty<MediaPathInfo>();
EnableAutomaticSeriesGrouping = true;
SeasonZeroDisplayName = "Specials";
+
+ PreferNonstandardArtistsTag = false;
+ UseCustomTagDelimiters = false;
+ CustomTagDelimiters = _defaultTagDelimiters;
+ DelimiterWhitelist = Array.Empty<string>();
}
public bool Enabled { get; set; } = true;
@@ -86,6 +97,10 @@ namespace MediaBrowser.Model.Configuration
public string[] SubtitleFetcherOrder { get; set; }
+ public string[] DisabledMediaSegmentProviders { get; set; }
+
+ public string[] MediaSegmentProvideOrder { get; set; }
+
public bool SkipSubtitlesIfEmbeddedSubtitlesPresent { get; set; }
public bool SkipSubtitlesIfAudioTrackMatches { get; set; }
@@ -99,10 +114,23 @@ namespace MediaBrowser.Model.Configuration
[DefaultValue(false)]
public bool SaveLyricsWithMedia { get; set; }
+ [DefaultValue(false)]
+ public bool SaveTrickplayWithMedia { get; set; }
+
public string[] DisabledLyricFetchers { get; set; }
public string[] LyricFetcherOrder { get; set; }
+ [DefaultValue(false)]
+ public bool PreferNonstandardArtistsTag { get; set; }
+
+ [DefaultValue(false)]
+ public bool UseCustomTagDelimiters { get; set; }
+
+ public string[] CustomTagDelimiters { get; set; }
+
+ public string[] DelimiterWhitelist { get; set; }
+
public bool AutomaticallyAddToCollection { get; set; }
public EmbeddedSubtitleOptions AllowEmbeddedSubtitles { get; set; }
diff --git a/MediaBrowser.Model/Configuration/MediaPathInfo.cs b/MediaBrowser.Model/Configuration/MediaPathInfo.cs
index a7bc43590..25a5d5606 100644
--- a/MediaBrowser.Model/Configuration/MediaPathInfo.cs
+++ b/MediaBrowser.Model/Configuration/MediaPathInfo.cs
@@ -16,7 +16,5 @@ namespace MediaBrowser.Model.Configuration
}
public string Path { get; set; }
-
- public string? NetworkPath { get; set; }
}
}
diff --git a/MediaBrowser.Model/Configuration/MetadataPluginType.cs b/MediaBrowser.Model/Configuration/MetadataPluginType.cs
index ef303726d..670d6e383 100644
--- a/MediaBrowser.Model/Configuration/MetadataPluginType.cs
+++ b/MediaBrowser.Model/Configuration/MetadataPluginType.cs
@@ -14,6 +14,7 @@ namespace MediaBrowser.Model.Configuration
MetadataFetcher,
MetadataSaver,
SubtitleFetcher,
- LyricFetcher
+ LyricFetcher,
+ MediaSegmentProvider
}
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 52f7e53b8..5ad588200 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -96,8 +96,6 @@ public class ServerConfiguration : BaseApplicationConfiguration
/// <value>The metadata path.</value>
public string MetadataPath { get; set; } = string.Empty;
- public string MetadataNetworkPath { get; set; } = string.Empty;
-
/// <summary>
/// Gets or sets the preferred metadata language.
/// </summary>
diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs
index 4962992a0..115598613 100644
--- a/MediaBrowser.Model/Devices/DeviceInfo.cs
+++ b/MediaBrowser.Model/Devices/DeviceInfo.cs
@@ -1,69 +1,84 @@
-#nullable disable
-#pragma warning disable CS1591
-
using System;
using MediaBrowser.Model.Session;
-namespace MediaBrowser.Model.Devices
+namespace MediaBrowser.Model.Devices;
+
+/// <summary>
+/// A class for device Information.
+/// </summary>
+public class DeviceInfo
{
- public class DeviceInfo
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DeviceInfo"/> class.
+ /// </summary>
+ public DeviceInfo()
{
- public DeviceInfo()
- {
- Capabilities = new ClientCapabilities();
- }
+ Capabilities = new ClientCapabilities();
+ }
- public string Name { get; set; }
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string? Name { get; set; }
- public string CustomName { get; set; }
+ /// <summary>
+ /// Gets or sets the custom name.
+ /// </summary>
+ /// <value>The custom name.</value>
+ public string? CustomName { get; set; }
- /// <summary>
- /// Gets or sets the access token.
- /// </summary>
- public string AccessToken { get; set; }
+ /// <summary>
+ /// Gets or sets the access token.
+ /// </summary>
+ /// <value>The access token.</value>
+ public string? AccessToken { get; set; }
- /// <summary>
- /// Gets or sets the identifier.
- /// </summary>
- /// <value>The identifier.</value>
- public string Id { get; set; }
+ /// <summary>
+ /// Gets or sets the identifier.
+ /// </summary>
+ /// <value>The identifier.</value>
+ public string? Id { get; set; }
- /// <summary>
- /// Gets or sets the last name of the user.
- /// </summary>
- /// <value>The last name of the user.</value>
- public string LastUserName { get; set; }
+ /// <summary>
+ /// Gets or sets the last name of the user.
+ /// </summary>
+ /// <value>The last name of the user.</value>
+ public string? LastUserName { get; set; }
- /// <summary>
- /// Gets or sets the name of the application.
- /// </summary>
- /// <value>The name of the application.</value>
- public string AppName { get; set; }
+ /// <summary>
+ /// Gets or sets the name of the application.
+ /// </summary>
+ /// <value>The name of the application.</value>
+ public string? AppName { get; set; }
- /// <summary>
- /// Gets or sets the application version.
- /// </summary>
- /// <value>The application version.</value>
- public string AppVersion { get; set; }
+ /// <summary>
+ /// Gets or sets the application version.
+ /// </summary>
+ /// <value>The application version.</value>
+ public string? AppVersion { get; set; }
- /// <summary>
- /// Gets or sets the last user identifier.
- /// </summary>
- /// <value>The last user identifier.</value>
- public Guid LastUserId { get; set; }
+ /// <summary>
+ /// Gets or sets the last user identifier.
+ /// </summary>
+ /// <value>The last user identifier.</value>
+ public Guid? LastUserId { get; set; }
- /// <summary>
- /// Gets or sets the date last modified.
- /// </summary>
- /// <value>The date last modified.</value>
- public DateTime DateLastActivity { get; set; }
+ /// <summary>
+ /// Gets or sets the date last modified.
+ /// </summary>
+ /// <value>The date last modified.</value>
+ public DateTime? DateLastActivity { get; set; }
- /// <summary>
- /// Gets or sets the capabilities.
- /// </summary>
- /// <value>The capabilities.</value>
- public ClientCapabilities Capabilities { get; set; }
+ /// <summary>
+ /// Gets or sets the capabilities.
+ /// </summary>
+ /// <value>The capabilities.</value>
+ public ClientCapabilities Capabilities { get; set; }
- public string IconUrl { get; set; }
- }
+ /// <summary>
+ /// Gets or sets the icon URL.
+ /// </summary>
+ /// <value>The icon URL.</value>
+ public string? IconUrl { get; set; }
}
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..995b7633a 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/MediaOptions.cs b/MediaBrowser.Model/Dlna/MediaOptions.cs
index eca971e95..6b26ca94b 100644
--- a/MediaBrowser.Model/Dlna/MediaOptions.cs
+++ b/MediaBrowser.Model/Dlna/MediaOptions.cs
@@ -50,6 +50,11 @@ namespace MediaBrowser.Model.Dlna
public bool AllowVideoStreamCopy { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether always burn in subtitles when transcoding.
+ /// </summary>
+ public bool AlwaysBurnInSubtitleWhenTranscoding { get; set; }
+
+ /// <summary>
/// Gets or sets the item id.
/// </summary>
public Guid ItemId { get; set; }
diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
index 5d7daa81a..1a636b240 100644
--- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
+++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
@@ -2,28 +2,33 @@
#pragma warning disable CS1591
using System;
+using System.Linq;
namespace MediaBrowser.Model.Dlna
{
public static class ResolutionNormalizer
{
- private static readonly ResolutionConfiguration[] Configurations =
- new[]
- {
- new ResolutionConfiguration(426, 320000),
- new ResolutionConfiguration(640, 400000),
- new ResolutionConfiguration(720, 950000),
- new ResolutionConfiguration(1280, 2500000),
- new ResolutionConfiguration(1920, 4000000),
- new ResolutionConfiguration(2560, 20000000),
- new ResolutionConfiguration(3840, 35000000)
- };
+ // Please note: all bitrate here are in the scale of SDR h264 bitrate at 30fps
+ private static readonly ResolutionConfiguration[] _configurations =
+ [
+ new ResolutionConfiguration(416, 365000),
+ new ResolutionConfiguration(640, 730000),
+ new ResolutionConfiguration(768, 1100000),
+ new ResolutionConfiguration(960, 3000000),
+ new ResolutionConfiguration(1280, 6000000),
+ new ResolutionConfiguration(1920, 13500000),
+ new ResolutionConfiguration(2560, 28000000),
+ new ResolutionConfiguration(3840, 50000000)
+ ];
public static ResolutionOptions Normalize(
int? inputBitrate,
int outputBitrate,
+ int h264EquivalentOutputBitrate,
int? maxWidth,
- int? maxHeight)
+ int? maxHeight,
+ float? targetFps,
+ bool isHdr = false) // We are not doing HDR transcoding for now, leave for future use
{
// If the bitrate isn't changing, then don't downscale the resolution
if (inputBitrate.HasValue && outputBitrate >= inputBitrate.Value)
@@ -38,16 +43,26 @@ namespace MediaBrowser.Model.Dlna
}
}
- var resolutionConfig = GetResolutionConfiguration(outputBitrate);
- if (resolutionConfig is not null)
+ var referenceBitrate = h264EquivalentOutputBitrate * (30.0f / (targetFps ?? 30.0f));
+
+ if (isHdr)
{
- var originvalValue = maxWidth;
+ referenceBitrate *= 0.8f;
+ }
- maxWidth = Math.Min(resolutionConfig.MaxWidth, maxWidth ?? resolutionConfig.MaxWidth);
- if (!originvalValue.HasValue || originvalValue.Value != maxWidth.Value)
- {
- maxHeight = null;
- }
+ var resolutionConfig = GetResolutionConfiguration(Convert.ToInt32(referenceBitrate));
+
+ if (resolutionConfig is null)
+ {
+ return new ResolutionOptions { MaxWidth = maxWidth, MaxHeight = maxHeight };
+ }
+
+ var originWidthValue = maxWidth;
+
+ maxWidth = Math.Min(resolutionConfig.MaxWidth, maxWidth ?? resolutionConfig.MaxWidth);
+ if (!originWidthValue.HasValue || originWidthValue.Value != maxWidth.Value)
+ {
+ maxHeight = null;
}
return new ResolutionOptions
@@ -59,19 +74,7 @@ namespace MediaBrowser.Model.Dlna
private static ResolutionConfiguration GetResolutionConfiguration(int outputBitrate)
{
- ResolutionConfiguration previousOption = null;
-
- foreach (var config in Configurations)
- {
- if (outputBitrate <= config.MaxBitrate)
- {
- return previousOption ?? config;
- }
-
- previousOption = config;
- }
-
- return null;
+ return _configurations.FirstOrDefault(config => outputBitrate <= config.MaxBitrate);
}
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 7f387bfaa..a25ddc367 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;
@@ -19,15 +20,17 @@ namespace MediaBrowser.Model.Dlna
{
// Aliases
internal const TranscodeReason ContainerReasons = TranscodeReason.ContainerNotSupported | TranscodeReason.ContainerBitrateExceedsLimit;
- internal const TranscodeReason AudioReasons = TranscodeReason.AudioCodecNotSupported | TranscodeReason.AudioBitrateNotSupported | TranscodeReason.AudioChannelsNotSupported | TranscodeReason.AudioProfileNotSupported | TranscodeReason.AudioSampleRateNotSupported | TranscodeReason.SecondaryAudioNotSupported | TranscodeReason.AudioBitDepthNotSupported | TranscodeReason.AudioIsExternal;
- internal const TranscodeReason VideoReasons = TranscodeReason.VideoCodecNotSupported | TranscodeReason.VideoResolutionNotSupported | TranscodeReason.AnamorphicVideoNotSupported | TranscodeReason.InterlacedVideoNotSupported | TranscodeReason.VideoBitDepthNotSupported | TranscodeReason.VideoBitrateNotSupported | TranscodeReason.VideoFramerateNotSupported | TranscodeReason.VideoLevelNotSupported | TranscodeReason.RefFramesNotSupported;
- internal const TranscodeReason DirectStreamReasons = AudioReasons | TranscodeReason.ContainerNotSupported;
+ internal const TranscodeReason AudioCodecReasons = TranscodeReason.AudioBitrateNotSupported | TranscodeReason.AudioChannelsNotSupported | TranscodeReason.AudioProfileNotSupported | TranscodeReason.AudioSampleRateNotSupported | TranscodeReason.SecondaryAudioNotSupported | TranscodeReason.AudioBitDepthNotSupported | TranscodeReason.AudioIsExternal;
+ internal const TranscodeReason AudioReasons = TranscodeReason.AudioCodecNotSupported | AudioCodecReasons;
+ internal const TranscodeReason VideoCodecReasons = TranscodeReason.VideoResolutionNotSupported | TranscodeReason.AnamorphicVideoNotSupported | TranscodeReason.InterlacedVideoNotSupported | TranscodeReason.VideoBitDepthNotSupported | TranscodeReason.VideoBitrateNotSupported | TranscodeReason.VideoFramerateNotSupported | TranscodeReason.VideoLevelNotSupported | TranscodeReason.RefFramesNotSupported | TranscodeReason.VideoRangeTypeNotSupported | TranscodeReason.VideoProfileNotSupported;
+ internal const TranscodeReason VideoReasons = TranscodeReason.VideoCodecNotSupported | VideoCodecReasons;
+ internal const TranscodeReason DirectStreamReasons = AudioReasons | TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecTagNotSupported;
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.
@@ -49,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)
@@ -62,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);
}
}
@@ -127,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;
@@ -224,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);
@@ -237,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);
@@ -352,7 +355,7 @@ namespace MediaBrowser.Model.Dlna
return TranscodeReason.VideoBitrateNotSupported;
case ProfileConditionValue.VideoCodecTag:
- return TranscodeReason.VideoCodecNotSupported;
+ return TranscodeReason.VideoCodecTagNotSupported;
case ProfileConditionValue.VideoFramerate:
return TranscodeReason.VideoFramerateNotSupported;
@@ -388,30 +391,31 @@ namespace MediaBrowser.Model.Dlna
/// <returns>The normalized input container.</returns>
public static string? NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null)
{
- if (string.IsNullOrEmpty(inputContainer))
+ // If the source is Live TV the inputContainer will be null until the mediasource is probed on first access
+ if (profile is null || string.IsNullOrEmpty(inputContainer) || !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;
+ }
+
+ if (directPlayProfile.SupportsContainer(format))
+ {
+ return format;
}
}
}
- return formats[0];
+ return inputContainer;
}
private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, MediaOptions options)
@@ -531,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
@@ -542,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)
@@ -621,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)
@@ -637,7 +640,8 @@ namespace MediaBrowser.Model.Dlna
RunTimeTicks = item.RunTimeTicks,
Context = options.Context,
DeviceProfile = options.Profile,
- SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles)
+ SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles),
+ AlwaysBurnInSubtitleWhenTranscoding = options.AlwaysBurnInSubtitleWhenTranscoding
};
var subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null;
@@ -649,7 +653,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)
@@ -700,7 +704,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)
{
@@ -711,7 +716,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)
@@ -719,7 +724,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);
@@ -751,8 +756,9 @@ 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 = GetVideoTranscodeProfile(item, options, videoStream, audioStream, candidateAudioStreams, subtitleStream, playlistItem);
- if (transcodingProfile is not null)
+ var (transcodingProfile, playMethod) = GetVideoTranscodeProfile(item, options, videoStream, audioStream, playlistItem);
+
+ if (transcodingProfile is not null && playMethod.HasValue)
{
SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile);
@@ -763,10 +769,9 @@ namespace MediaBrowser.Model.Dlna
if (subtitleStream is not null)
{
var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
-
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
playlistItem.SubtitleFormat = subtitleProfile.Format;
- playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
+ playlistItem.SubtitleCodecs = [subtitleProfile.Format];
}
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
@@ -790,58 +795,94 @@ namespace MediaBrowser.Model.Dlna
return playlistItem;
}
- private TranscodingProfile? GetVideoTranscodeProfile(
+ private (TranscodingProfile? Profile, PlayMethod? PlayMethod) GetVideoTranscodeProfile(
MediaSourceInfo item,
MediaOptions options,
MediaStream? videoStream,
MediaStream? audioStream,
- IEnumerable<MediaStream> candidateAudioStreams,
- MediaStream? subtitleStream,
StreamInfo playlistItem)
{
if (!(item.SupportsTranscoding || item.SupportsDirectStream))
{
- return null;
+ return (null, null);
}
var transcodingProfiles = options.Profile.TranscodingProfiles
.Where(i => i.Type == playlistItem.MediaType && i.Context == options.Context);
- if (options.AllowVideoStreamCopy)
+ if (item.UseMostCompatibleTranscodingProfile)
{
- // prefer direct copy profile
- float videoFramerate = videoStream?.AverageFrameRate ?? videoStream?.RealFrameRate ?? 0;
- TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp;
- int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
- int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
+ transcodingProfiles = transcodingProfiles.Where(i => string.Equals(i.Container, "ts", StringComparison.OrdinalIgnoreCase));
+ }
+
+ var videoCodec = videoStream?.Codec;
+ float videoFramerate = videoStream?.ReferenceFrameRate ?? 0;
+ TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp;
+ int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
+ int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
+
+ var audioCodec = audioStream?.Codec;
+ var audioProfile = audioStream?.Profile;
+ var audioChannels = audioStream?.Channels;
+ var audioBitrate = audioStream?.BitRate;
+ var audioSampleRate = audioStream?.SampleRate;
+ var audioBitDepth = audioStream?.BitDepth;
- transcodingProfiles = transcodingProfiles.ToLookup(transcodingProfile =>
+ var analyzedProfiles = transcodingProfiles
+ .Select(transcodingProfile =>
{
- var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
+ var rank = (Video: 3, Audio: 3);
+
+ var container = transcodingProfile.Container;
+
+ if (options.AllowVideoStreamCopy)
+ {
+ if (ContainerHelper.ContainsContainer(transcodingProfile.VideoCodec, videoCodec))
+ {
+ var appliedVideoConditions = options.Profile.CodecProfiles
+ .Where(i => i.Type == CodecType.Video &&
+ i.ContainsAnyCodec(videoCodec, container) &&
+ i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
+ .Select(i =>
+ i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
+
+ // An empty appliedVideoConditions means that the codec has no conditions for the current video stream
+ var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
+ rank.Video = conditionsSatisfied ? 1 : 2;
+ }
+ }
- if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec))
+ if (options.AllowAudioStreamCopy)
{
- var videoCodec = videoStream?.Codec;
- var container = transcodingProfile.Container;
- var appliedVideoConditions = options.Profile.CodecProfiles
- .Where(i => i.Type == CodecType.Video &&
- i.ContainsAnyCodec(videoCodec, container) &&
- i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
- .Select(i =>
- i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.VideoRangeType, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
-
- // An empty appliedVideoConditions means that the codec has no conditions for the current video stream
- var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
- return conditionsSatisfied ? 1 : 2;
+ if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec))
+ {
+ var appliedVideoConditions = options.Profile.CodecProfiles
+ .Where(i => i.Type == CodecType.VideoAudio &&
+ i.ContainsAnyCodec(audioCodec, container) &&
+ i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false)))
+ .Select(i =>
+ i.Conditions.All(condition => ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false)));
+
+ // An empty appliedVideoConditions means that the codec has no conditions for the current audio stream
+ var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
+ rank.Audio = conditionsSatisfied ? 1 : 2;
+ }
+ }
+
+ PlayMethod playMethod = PlayMethod.Transcode;
+
+ if (rank.Video == 1)
+ {
+ playMethod = PlayMethod.DirectStream;
}
- return 3;
+ return (Profile: transcodingProfile, PlayMethod: playMethod, Rank: rank);
})
- .OrderBy(lookup => lookup.Key)
- .SelectMany(lookup => lookup);
- }
+ .OrderBy(analysis => analysis.Rank);
- return transcodingProfiles.FirstOrDefault();
+ var profileMatch = analyzedProfiles.FirstOrDefault();
+
+ return (profileMatch.Profile, profileMatch.PlayMethod);
}
private void BuildStreamVideoItem(
@@ -856,26 +897,24 @@ 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;
// Copy video codec options as a starting point, this applies to transcode and direct-stream
- playlistItem.MaxFramerate = videoStream?.AverageFrameRate;
+ playlistItem.MaxFramerate = videoStream?.ReferenceFrameRate;
var qualifier = videoStream?.Codec;
if (videoStream?.Level is not null)
{
@@ -893,22 +932,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;
@@ -925,7 +970,8 @@ namespace MediaBrowser.Model.Dlna
{
audioStream = directAudioStream;
playlistItem.AudioStreamIndex = audioStream.Index;
- playlistItem.AudioCodecs = new[] { audioStream.Codec };
+ audioCodecs = [audioStream.Codec];
+ playlistItem.AudioCodecs = audioCodecs;
// Copy matching audio codec options
playlistItem.AudioSampleRate = audioStream.SampleRate;
@@ -949,7 +995,7 @@ namespace MediaBrowser.Model.Dlna
double? videoLevel = videoStream?.Level;
string? videoProfile = videoStream?.Profile;
VideoRangeType? videoRangeType = videoStream?.VideoRangeType;
- float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
+ float videoFramerate = videoStream is null ? 0 : videoStream.ReferenceFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
string? videoCodecTag = videoStream?.CodecTag;
@@ -966,19 +1012,17 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video &&
- i.ContainsAnyCodec(videoStream?.Codec, 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)
{
- var transcodingVideoCodecs = ContainerProfile.SplitValue(videoCodec);
- foreach (var transcodingVideoCodec in transcodingVideoCodecs)
+ 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;
}
}
@@ -999,15 +1043,14 @@ namespace MediaBrowser.Model.Dlna
var appliedAudioConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.VideoAudio &&
- i.ContainsAnyCodec(audioStream?.Codec, 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)
{
- var transcodingAudioCodecs = ContainerProfile.SplitValue(audioCodec);
- foreach (var transcodingAudioCodec in transcodingAudioCodecs)
+ foreach (var transcodingAudioCodec in playlistItem.AudioCodecs)
{
if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container))
{
@@ -1077,9 +1120,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);
@@ -1096,7 +1139,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
@@ -1104,8 +1147,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);
@@ -1208,7 +1251,7 @@ namespace MediaBrowser.Model.Dlna
double? videoLevel = videoStream?.Level;
string? videoProfile = videoStream?.Profile;
VideoRangeType? videoRangeType = videoStream?.VideoRangeType;
- float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
+ float videoFramerate = videoStream is null ? 0 : videoStream.ReferenceFrameRate ?? 0;
bool? isAnamorphic = videoStream?.IsAnamorphic;
bool? isInterlaced = videoStream?.IsInterlaced;
string? videoCodecTag = videoStream?.CodecTag;
@@ -1244,7 +1287,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;
@@ -1261,25 +1304,8 @@ namespace MediaBrowser.Model.Dlna
}
}
- var rankings = new[] { VideoReasons, AudioReasons, 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;
+ TranscodeReason[] rankings = [TranscodeReason.VideoCodecNotSupported, VideoCodecReasons, TranscodeReason.AudioCodecNotSupported, AudioCodecReasons, ContainerReasons];
// Check DirectPlay profiles to see if it can be direct played
var analyzedProfiles = profile.DirectPlayProfiles
@@ -1345,7 +1371,8 @@ namespace MediaBrowser.Model.Dlna
playMethod = PlayMethod.DirectStream;
}
- var ranked = rank(ref failureReasons);
+ var ranked = GetRank(ref failureReasons, rankings);
+
return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
})
.OrderByDescending(analysis => analysis.Result.PlayMethod)
@@ -1364,7 +1391,7 @@ namespace MediaBrowser.Model.Dlna
var failureReasons = analyzedProfiles[false]
.Select(analysis => analysis.Result)
- .Where(result => !containerSupported || (result.TranscodeReason & TranscodeReason.ContainerNotSupported) == 0)
+ .Where(result => !containerSupported || !result.TranscodeReason.HasFlag(TranscodeReason.ContainerNotSupported))
.FirstOrDefault().TranscodeReason;
if (failureReasons == 0)
{
@@ -1420,7 +1447,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,
@@ -1446,7 +1473,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer))
+ if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
{
continue;
}
@@ -1475,7 +1502,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- if (!ContainerProfile.ContainsContainer(profile.Container, outputContainer))
+ if (!ContainerHelper.ContainsContainer(profile.Container, outputContainer))
{
continue;
}
@@ -1506,17 +1533,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;
}
@@ -2219,5 +2241,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 c8a341d41..1ae4e1962 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,1308 @@ 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; }
+
+ /// <summary>
+ /// Gets or sets the global maximum audio channels.
+ /// </summary>
+ /// <value>The global maximum audio channels.</value>
+ public int? GlobalMaxAudioChannels { get; set; }
- public int? GlobalMaxAudioChannels { get; set; }
+ /// <summary>
+ /// Gets or sets the audio bitrate.
+ /// </summary>
+ /// <value>The audio bitrate.</value>
+ public int? AudioBitrate { get; set; }
- public int? AudioBitrate { get; set; }
+ /// <summary>
+ /// Gets or sets the audio sample rate.
+ /// </summary>
+ /// <value>The audio sample rate.</value>
+ public int? AudioSampleRate { get; set; }
- public int? AudioSampleRate { get; set; }
+ /// <summary>
+ /// Gets or sets the video bitrate.
+ /// </summary>
+ /// <value>The video bitrate.</value>
+ public int? VideoBitrate { get; set; }
- public int? VideoBitrate { get; set; }
+ /// <summary>
+ /// Gets or sets the maximum output width.
+ /// </summary>
+ /// <value>The output width.</value>
+ public int? MaxWidth { get; set; }
- public int? MaxWidth { get; set; }
+ /// <summary>
+ /// Gets or sets the maximum output height.
+ /// </summary>
+ /// <value>The maximum output height.</value>
+ public int? MaxHeight { get; set; }
- public int? MaxHeight { get; set; }
+ /// <summary>
+ /// Gets or sets the maximum framerate.
+ /// </summary>
+ /// <value>The maximum framerate.</value>
+ public float? MaxFramerate { get; set; }
- public float? MaxFramerate { get; set; }
+ /// <summary>
+ /// Gets or sets the device profile.
+ /// </summary>
+ /// <value>The device profile.</value>
+ public required DeviceProfile DeviceProfile { get; set; }
- public required DeviceProfile DeviceProfile { get; set; }
+ /// <summary>
+ /// Gets or sets the device profile id.
+ /// </summary>
+ /// <value>The device profile id.</value>
+ public string? DeviceProfileId { get; set; }
- public string? DeviceProfileId { get; set; }
+ /// <summary>
+ /// Gets or sets the device id.
+ /// </summary>
+ /// <value>The device id.</value>
+ public string? DeviceId { get; set; }
- public string? DeviceId { get; set; }
+ /// <summary>
+ /// Gets or sets the runtime ticks.
+ /// </summary>
+ /// <value>The runtime ticks.</value>
+ public long? RunTimeTicks { get; set; }
- public long? RunTimeTicks { get; set; }
+ /// <summary>
+ /// Gets or sets the transcode seek info.
+ /// </summary>
+ /// <value>The transcode seek info.</value>
+ public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
- public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether content length should be estimated.
+ /// </summary>
+ public bool EstimateContentLength { get; set; }
- public bool EstimateContentLength { get; set; }
+ /// <summary>
+ /// Gets or sets the media source info.
+ /// </summary>
+ /// <value>The media source info.</value>
+ public MediaSourceInfo? MediaSource { get; set; }
- public MediaSourceInfo? MediaSource { get; set; }
+ /// <summary>
+ /// Gets or sets the subtitle codecs.
+ /// </summary>
+ /// <value>The subtitle codecs.</value>
+ public IReadOnlyList<string> SubtitleCodecs { get; set; }
- public string[] SubtitleCodecs { get; set; }
+ /// <summary>
+ /// Gets or sets the subtitle delivery method.
+ /// </summary>
+ /// <value>The subtitle delivery method.</value>
+ public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
- public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
+ /// <summary>
+ /// Gets or sets the subtitle format.
+ /// </summary>
+ /// <value>The subtitle format.</value>
+ public string? SubtitleFormat { get; set; }
- public string? SubtitleFormat { get; set; }
+ /// <summary>
+ /// Gets or sets the play session id.
+ /// </summary>
+ /// <value>The play session id.</value>
+ public string? PlaySessionId { get; set; }
- public string? PlaySessionId { get; set; }
+ /// <summary>
+ /// Gets or sets the transcode reasons.
+ /// </summary>
+ /// <value>The transcode reasons.</value>
+ public TranscodeReason TranscodeReasons { get; set; }
- public TranscodeReason TranscodeReasons { get; set; }
+ /// <summary>
+ /// Gets the stream options.
+ /// </summary>
+ /// <value>The stream options.</value>
+ public Dictionary<string, string> StreamOptions { get; private set; }
- public Dictionary<string, string> StreamOptions { get; private set; }
+ /// <summary>
+ /// Gets the media source id.
+ /// </summary>
+ /// <value>The media source id.</value>
+ public string? MediaSourceId => MediaSource?.Id;
- public string? MediaSourceId => MediaSource?.Id;
+ /// <summary>
+ /// Gets or sets a value indicating whether audio VBR encoding is enabled.
+ /// </summary>
+ public bool EnableAudioVbrEncoding { get; set; }
- public bool EnableAudioVbrEncoding { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether always burn in subtitles when transcoding.
+ /// </summary>
+ public bool AlwaysBurnInSubtitleWhenTranscoding { 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 is null ? null : stream.AverageFrameRate ?? stream.RealFrameRate;
+ 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.AlwaysBurnInSubtitleWhenTranscoding || item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+
+ list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+
+ long startPositionTicks = item.StartPositionTicks;
+
+ if (item.SubProtocol == MediaStreamProtocol.hls)
+ {
+ list.Add(new NameValuePair("StartTimeTicks", string.Empty));
+ }
+ 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("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
- list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : 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/Dto/ClientCapabilitiesDto.cs b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs
new file mode 100644
index 000000000..5963ed270
--- /dev/null
+++ b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs
@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Jellyfin.Data.Enums;
+using Jellyfin.Extensions.Json.Converters;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Model.Dto;
+
+/// <summary>
+/// Client capabilities dto.
+/// </summary>
+public class ClientCapabilitiesDto
+{
+ /// <summary>
+ /// Gets or sets the list of playable media types.
+ /// </summary>
+ [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
+ public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = [];
+
+ /// <summary>
+ /// Gets or sets the list of supported commands.
+ /// </summary>
+ [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
+ public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = [];
+
+ /// <summary>
+ /// Gets or sets a value indicating whether session supports media control.
+ /// </summary>
+ public bool SupportsMediaControl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether session supports a persistent identifier.
+ /// </summary>
+ public bool SupportsPersistentIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the device profile.
+ /// </summary>
+ public DeviceProfile? DeviceProfile { get; set; }
+
+ /// <summary>
+ /// Gets or sets the app store url.
+ /// </summary>
+ public string? AppStoreUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets the icon url.
+ /// </summary>
+ public string? IconUrl { get; set; }
+
+ /// <summary>
+ /// Convert the dto to the full <see cref="ClientCapabilities"/> model.
+ /// </summary>
+ /// <returns>The converted <see cref="ClientCapabilities"/> model.</returns>
+ public ClientCapabilities ToClientCapabilities()
+ {
+ return new ClientCapabilities
+ {
+ PlayableMediaTypes = PlayableMediaTypes,
+ SupportedCommands = SupportedCommands,
+ SupportsMediaControl = SupportsMediaControl,
+ SupportsPersistentIdentifier = SupportsPersistentIdentifier,
+ DeviceProfile = DeviceProfile,
+ AppStoreUrl = AppStoreUrl,
+ IconUrl = IconUrl
+ };
+ }
+}
diff --git a/MediaBrowser.Model/Dto/DeviceInfoDto.cs b/MediaBrowser.Model/Dto/DeviceInfoDto.cs
new file mode 100644
index 000000000..ac7a731a9
--- /dev/null
+++ b/MediaBrowser.Model/Dto/DeviceInfoDto.cs
@@ -0,0 +1,83 @@
+using System;
+
+namespace MediaBrowser.Model.Dto;
+
+/// <summary>
+/// A DTO representing device information.
+/// </summary>
+public class DeviceInfoDto
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DeviceInfoDto"/> class.
+ /// </summary>
+ public DeviceInfoDto()
+ {
+ Capabilities = new ClientCapabilitiesDto();
+ }
+
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string? Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the custom name.
+ /// </summary>
+ /// <value>The custom name.</value>
+ public string? CustomName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the access token.
+ /// </summary>
+ /// <value>The access token.</value>
+ public string? AccessToken { get; set; }
+
+ /// <summary>
+ /// Gets or sets the identifier.
+ /// </summary>
+ /// <value>The identifier.</value>
+ public string? Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the last name of the user.
+ /// </summary>
+ /// <value>The last name of the user.</value>
+ public string? LastUserName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name of the application.
+ /// </summary>
+ /// <value>The name of the application.</value>
+ public string? AppName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the application version.
+ /// </summary>
+ /// <value>The application version.</value>
+ public string? AppVersion { get; set; }
+
+ /// <summary>
+ /// Gets or sets the last user identifier.
+ /// </summary>
+ /// <value>The last user identifier.</value>
+ public Guid? LastUserId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the date last modified.
+ /// </summary>
+ /// <value>The date last modified.</value>
+ public DateTime? DateLastActivity { get; set; }
+
+ /// <summary>
+ /// Gets or sets the capabilities.
+ /// </summary>
+ /// <value>The capabilities.</value>
+ public ClientCapabilitiesDto Capabilities { get; set; }
+
+ /// <summary>
+ /// Gets or sets the icon URL.
+ /// </summary>
+ /// <value>The icon URL.</value>
+ public string? IconUrl { get; set; }
+}
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index 1c6037325..eff2e09da 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Dlna;
@@ -24,6 +25,7 @@ namespace MediaBrowser.Model.Dto
SupportsDirectStream = true;
SupportsDirectPlay = true;
SupportsProbing = true;
+ UseMostCompatibleTranscodingProfile = false;
}
public MediaProtocol Protocol { get; set; }
@@ -70,6 +72,9 @@ namespace MediaBrowser.Model.Dto
public bool IsInfiniteStream { get; set; }
+ [DefaultValue(false)]
+ public bool UseMostCompatibleTranscodingProfile { get; set; }
+
public bool RequiresOpening { get; set; }
public string OpenToken { get; set; }
@@ -98,6 +103,8 @@ namespace MediaBrowser.Model.Dto
public int? Bitrate { get; set; }
+ public int? FallbackMaxStreamingBitrate { get; set; }
+
public TransportStreamTimestamp? Timestamp { get; set; }
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
diff --git a/MediaBrowser.Model/Dto/PlaylistDto.cs b/MediaBrowser.Model/Dto/PlaylistDto.cs
new file mode 100644
index 000000000..d4de75a78
--- /dev/null
+++ b/MediaBrowser.Model/Dto/PlaylistDto.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Model.Dto;
+
+/// <summary>
+/// DTO for playlists.
+/// </summary>
+public class PlaylistDto
+{
+ /// <summary>
+ /// Gets or sets a value indicating whether the playlist is publicly readable.
+ /// </summary>
+ public bool OpenAccess { get; set; }
+
+ /// <summary>
+ /// Gets or sets the share permissions.
+ /// </summary>
+ public required IReadOnlyList<PlaylistUserPermissions> Shares { get; set; }
+
+ /// <summary>
+ /// Gets or sets the item ids.
+ /// </summary>
+ public required IReadOnlyList<Guid> ItemIds { get; set; }
+}
diff --git a/MediaBrowser.Model/Dto/SessionInfoDto.cs b/MediaBrowser.Model/Dto/SessionInfoDto.cs
new file mode 100644
index 000000000..2496c933a
--- /dev/null
+++ b/MediaBrowser.Model/Dto/SessionInfoDto.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Collections.Generic;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Model.Dto;
+
+/// <summary>
+/// Session info DTO.
+/// </summary>
+public class SessionInfoDto
+{
+ /// <summary>
+ /// Gets or sets the play state.
+ /// </summary>
+ /// <value>The play state.</value>
+ public PlayerStateInfo? PlayState { get; set; }
+
+ /// <summary>
+ /// Gets or sets the additional users.
+ /// </summary>
+ /// <value>The additional users.</value>
+ public IReadOnlyList<SessionUserInfo>? AdditionalUsers { get; set; }
+
+ /// <summary>
+ /// Gets or sets the client capabilities.
+ /// </summary>
+ /// <value>The client capabilities.</value>
+ public ClientCapabilitiesDto? Capabilities { get; set; }
+
+ /// <summary>
+ /// Gets or sets the remote end point.
+ /// </summary>
+ /// <value>The remote end point.</value>
+ public string? RemoteEndPoint { get; set; }
+
+ /// <summary>
+ /// Gets or sets the playable media types.
+ /// </summary>
+ /// <value>The playable media types.</value>
+ public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = [];
+
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ public string? Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user id.
+ /// </summary>
+ /// <value>The user id.</value>
+ public Guid UserId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the username.
+ /// </summary>
+ /// <value>The username.</value>
+ public string? UserName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type of the client.
+ /// </summary>
+ /// <value>The type of the client.</value>
+ public string? Client { get; set; }
+
+ /// <summary>
+ /// Gets or sets the last activity date.
+ /// </summary>
+ /// <value>The last activity date.</value>
+ public DateTime LastActivityDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the last playback check in.
+ /// </summary>
+ /// <value>The last playback check in.</value>
+ public DateTime LastPlaybackCheckIn { get; set; }
+
+ /// <summary>
+ /// Gets or sets the last paused date.
+ /// </summary>
+ /// <value>The last paused date.</value>
+ public DateTime? LastPausedDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name of the device.
+ /// </summary>
+ /// <value>The name of the device.</value>
+ public string? DeviceName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type of the device.
+ /// </summary>
+ /// <value>The type of the device.</value>
+ public string? DeviceType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the now playing item.
+ /// </summary>
+ /// <value>The now playing item.</value>
+ public BaseItemDto? NowPlayingItem { get; set; }
+
+ /// <summary>
+ /// Gets or sets the now viewing item.
+ /// </summary>
+ /// <value>The now viewing item.</value>
+ public BaseItemDto? NowViewingItem { get; set; }
+
+ /// <summary>
+ /// Gets or sets the device id.
+ /// </summary>
+ /// <value>The device id.</value>
+ public string? DeviceId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the application version.
+ /// </summary>
+ /// <value>The application version.</value>
+ public string? ApplicationVersion { get; set; }
+
+ /// <summary>
+ /// Gets or sets the transcoding info.
+ /// </summary>
+ /// <value>The transcoding info.</value>
+ public TranscodingInfo? TranscodingInfo { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this session is active.
+ /// </summary>
+ /// <value><c>true</c> if this session is active; otherwise, <c>false</c>.</value>
+ public bool IsActive { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the session supports media control.
+ /// </summary>
+ /// <value><c>true</c> if this session supports media control; otherwise, <c>false</c>.</value>
+ public bool SupportsMediaControl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the session supports remote control.
+ /// </summary>
+ /// <value><c>true</c> if this session supports remote control; otherwise, <c>false</c>.</value>
+ public bool SupportsRemoteControl { get; set; }
+
+ /// <summary>
+ /// Gets or sets the now playing queue.
+ /// </summary>
+ /// <value>The now playing queue.</value>
+ public IReadOnlyList<QueueItem>? NowPlayingQueue { get; set; }
+
+ /// <summary>
+ /// Gets or sets the now playing queue full items.
+ /// </summary>
+ /// <value>The now playing queue full items.</value>
+ public IReadOnlyList<BaseItemDto>? NowPlayingQueueFullItems { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the session has a custom device name.
+ /// </summary>
+ /// <value><c>true</c> if this session has a custom device name; otherwise, <c>false</c>.</value>
+ public bool HasCustomDeviceName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the playlist item id.
+ /// </summary>
+ /// <value>The splaylist item id.</value>
+ public string? PlaylistItemId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the server id.
+ /// </summary>
+ /// <value>The server id.</value>
+ public string? ServerId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user primary image tag.
+ /// </summary>
+ /// <value>The user primary image tag.</value>
+ public string? UserPrimaryImageTag { get; set; }
+
+ /// <summary>
+ /// Gets or sets the supported commands.
+ /// </summary>
+ /// <value>The supported commands.</value>
+ public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = [];
+}
diff --git a/MediaBrowser.Model/Entities/DeinterlaceMethod.cs b/MediaBrowser.Model/Entities/DeinterlaceMethod.cs
new file mode 100644
index 000000000..d05aac433
--- /dev/null
+++ b/MediaBrowser.Model/Entities/DeinterlaceMethod.cs
@@ -0,0 +1,19 @@
+#pragma warning disable SA1300 // Lowercase required for backwards compat.
+
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// Enum containing deinterlace methods.
+/// </summary>
+public enum DeinterlaceMethod
+{
+ /// <summary>
+ /// YADIF.
+ /// </summary>
+ yadif = 0,
+
+ /// <summary>
+ /// BWDIF.
+ /// </summary>
+ bwdif = 1
+}
diff --git a/MediaBrowser.Model/Entities/EncoderPreset.cs b/MediaBrowser.Model/Entities/EncoderPreset.cs
new file mode 100644
index 000000000..74c071433
--- /dev/null
+++ b/MediaBrowser.Model/Entities/EncoderPreset.cs
@@ -0,0 +1,64 @@
+#pragma warning disable SA1300 // Lowercase required for backwards compat.
+
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// Enum containing encoder presets.
+/// </summary>
+public enum EncoderPreset
+{
+ /// <summary>
+ /// Auto preset.
+ /// </summary>
+ auto = 0,
+
+ /// <summary>
+ /// Placebo preset.
+ /// </summary>
+ placebo = 1,
+
+ /// <summary>
+ /// Veryslow preset.
+ /// </summary>
+ veryslow = 2,
+
+ /// <summary>
+ /// Slower preset.
+ /// </summary>
+ slower = 3,
+
+ /// <summary>
+ /// Slow preset.
+ /// </summary>
+ slow = 4,
+
+ /// <summary>
+ /// Medium preset.
+ /// </summary>
+ medium = 5,
+
+ /// <summary>
+ /// Fast preset.
+ /// </summary>
+ fast = 6,
+
+ /// <summary>
+ /// Faster preset.
+ /// </summary>
+ faster = 7,
+
+ /// <summary>
+ /// Veryfast preset.
+ /// </summary>
+ veryfast = 8,
+
+ /// <summary>
+ /// Superfast preset.
+ /// </summary>
+ superfast = 9,
+
+ /// <summary>
+ /// Ultrafast preset.
+ /// </summary>
+ ultrafast = 10
+}
diff --git a/MediaBrowser.Model/Entities/HardwareAccelerationType.cs b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs
new file mode 100644
index 000000000..198a2e00f
--- /dev/null
+++ b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs
@@ -0,0 +1,49 @@
+#pragma warning disable SA1300 // Lowercase required for backwards compat.
+
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// Enum containing hardware acceleration types.
+/// </summary>
+public enum HardwareAccelerationType
+{
+ /// <summary>
+ /// Software accelleration.
+ /// </summary>
+ none = 0,
+
+ /// <summary>
+ /// AMD AMF.
+ /// </summary>
+ amf = 1,
+
+ /// <summary>
+ /// Intel Quick Sync Video.
+ /// </summary>
+ qsv = 2,
+
+ /// <summary>
+ /// NVIDIA NVENC.
+ /// </summary>
+ nvenc = 3,
+
+ /// <summary>
+ /// Video4Linux2 V4L2M2M.
+ /// </summary>
+ v4l2m2m = 4,
+
+ /// <summary>
+ /// Video Acceleration API (VAAPI).
+ /// </summary>
+ vaapi = 5,
+
+ /// <summary>
+ /// Video ToolBox.
+ /// </summary>
+ videotoolbox = 6,
+
+ /// <summary>
+ /// Rockchip Media Process Platform (RKMPP).
+ /// </summary>
+ rkmpp = 7
+}
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index a0e8c39be..85c1f797b 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -200,7 +200,8 @@ namespace MediaBrowser.Model.Entities
|| dvProfile == 5
|| dvProfile == 7
|| dvProfile == 8
- || dvProfile == 9))
+ || dvProfile == 9
+ || dvProfile == 10))
{
var title = "Dolby Vision Profile " + dvProfile;
@@ -526,6 +527,23 @@ namespace MediaBrowser.Model.Entities
public float? RealFrameRate { get; set; }
/// <summary>
+ /// Gets the framerate used as reference.
+ /// Prefer AverageFrameRate, if that is null or an unrealistic value
+ /// then fallback to RealFrameRate.
+ /// </summary>
+ /// <value>The reference frame rate.</value>
+ public float? ReferenceFrameRate
+ {
+ get
+ {
+ // In some cases AverageFrameRate for videos will be read as 1000fps even if it is not.
+ // This is probably due to a library compatability issue.
+ // See https://github.com/jellyfin/jellyfin/pull/12603#discussion_r1748044018 for more info.
+ return AverageFrameRate < 1000 ? AverageFrameRate : RealFrameRate;
+ }
+ }
+
+ /// <summary>
/// Gets or sets the profile.
/// </summary>
/// <value>The profile.</value>
@@ -760,7 +778,7 @@ namespace MediaBrowser.Model.Entities
var blPresentFlag = BlPresentFlag == 1;
var dvBlCompatId = DvBlSignalCompatibilityId;
- var isDoViProfile = dvProfile == 5 || dvProfile == 7 || dvProfile == 8;
+ var isDoViProfile = dvProfile == 5 || dvProfile == 7 || dvProfile == 8 || dvProfile == 10;
var isDoViFlag = rpuPresentFlag && blPresentFlag && (dvBlCompatId == 0 || dvBlCompatId == 1 || dvBlCompatId == 4 || dvBlCompatId == 2 || dvBlCompatId == 6);
if ((isDoViProfile && isDoViFlag)
@@ -783,6 +801,17 @@ namespace MediaBrowser.Model.Entities
_ => (VideoRange.SDR, VideoRangeType.SDR)
},
7 => (VideoRange.HDR, VideoRangeType.HDR10),
+ 10 => dvBlCompatId switch
+ {
+ 0 => (VideoRange.HDR, VideoRangeType.DOVI),
+ 1 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
+ 2 => (VideoRange.SDR, VideoRangeType.DOVIWithSDR),
+ 4 => (VideoRange.HDR, VideoRangeType.DOVIWithHLG),
+ // While not in Dolby Spec, Profile 8 CCid 6 media are possible to create, and since CCid 6 stems from Bluray (Profile 7 originally) an HDR10 base layer is guaranteed to exist.
+ 6 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
+ // There is no other case to handle here as per Dolby Spec. Default case included for completeness and linting purposes
+ _ => (VideoRange.SDR, VideoRangeType.SDR)
+ },
_ => (VideoRange.SDR, VideoRangeType.SDR)
};
}
diff --git a/MediaBrowser.Model/Entities/TonemappingAlgorithm.cs b/MediaBrowser.Model/Entities/TonemappingAlgorithm.cs
new file mode 100644
index 000000000..488006e0b
--- /dev/null
+++ b/MediaBrowser.Model/Entities/TonemappingAlgorithm.cs
@@ -0,0 +1,49 @@
+#pragma warning disable SA1300 // Lowercase required for backwards compat.
+
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// Enum containing tonemapping algorithms.
+/// </summary>
+public enum TonemappingAlgorithm
+{
+ /// <summary>
+ /// None.
+ /// </summary>
+ none = 0,
+
+ /// <summary>
+ /// Clip.
+ /// </summary>
+ clip = 1,
+
+ /// <summary>
+ /// Linear.
+ /// </summary>
+ linear = 2,
+
+ /// <summary>
+ /// Gamma.
+ /// </summary>
+ gamma = 3,
+
+ /// <summary>
+ /// Reinhard.
+ /// </summary>
+ reinhard = 4,
+
+ /// <summary>
+ /// Hable.
+ /// </summary>
+ hable = 5,
+
+ /// <summary>
+ /// Mobius.
+ /// </summary>
+ mobius = 6,
+
+ /// <summary>
+ /// BT2390.
+ /// </summary>
+ bt2390 = 7
+}
diff --git a/MediaBrowser.Model/Entities/TonemappingMode.cs b/MediaBrowser.Model/Entities/TonemappingMode.cs
new file mode 100644
index 000000000..e10a0b4ad
--- /dev/null
+++ b/MediaBrowser.Model/Entities/TonemappingMode.cs
@@ -0,0 +1,34 @@
+#pragma warning disable SA1300 // Lowercase required for backwards compat.
+
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// Enum containing tonemapping modes.
+/// </summary>
+public enum TonemappingMode
+{
+ /// <summary>
+ /// Auto.
+ /// </summary>
+ auto = 0,
+
+ /// <summary>
+ /// Max.
+ /// </summary>
+ max = 1,
+
+ /// <summary>
+ /// RGB.
+ /// </summary>
+ rgb = 2,
+
+ /// <summary>
+ /// Lum.
+ /// </summary>
+ lum = 3,
+
+ /// <summary>
+ /// ITP.
+ /// </summary>
+ itp = 4
+}
diff --git a/MediaBrowser.Model/Entities/TonemappingRange.cs b/MediaBrowser.Model/Entities/TonemappingRange.cs
new file mode 100644
index 000000000..b1446b81c
--- /dev/null
+++ b/MediaBrowser.Model/Entities/TonemappingRange.cs
@@ -0,0 +1,24 @@
+#pragma warning disable SA1300 // Lowercase required for backwards compat.
+
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// Enum containing tonemapping ranges.
+/// </summary>
+public enum TonemappingRange
+{
+ /// <summary>
+ /// Auto.
+ /// </summary>
+ auto = 0,
+
+ /// <summary>
+ /// TV.
+ /// </summary>
+ tv = 1,
+
+ /// <summary>
+ /// PC.
+ /// </summary>
+ pc = 2
+}
diff --git a/MediaBrowser.Model/Extensions/ContainerHelper.cs b/MediaBrowser.Model/Extensions/ContainerHelper.cs
new file mode 100644
index 000000000..c86328ba6
--- /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 (!profile.IsEmpty && container.Equals(profile, StringComparison.OrdinalIgnoreCase))
+ {
+ return !isNegativeList;
+ }
+ }
+ }
+ }
+
+ return isNegativeList;
+ }
+
+ /// <summary>
+ /// Compares two containers, returning <paramref name="isNegativeList"/> if an item in <paramref name="inputContainer"/>
+ /// does not exist in <paramref name="profileContainers"/>.
+ /// </summary>
+ /// <param name="profileContainers">The profile containers being matched searched.</param>
+ /// <param name="isNegativeList">The boolean result to return if a match is not found.</param>
+ /// <param name="inputContainer">The comma-delimited string being matched.</param>
+ /// <returns>The result of the operation.</returns>
+ public static bool ContainsContainer(IReadOnlyList<string>? profileContainers, bool isNegativeList, string inputContainer)
+ {
+ if (profileContainers is null)
+ {
+ // Empty profiles always support all containers/codecs.
+ return true;
+ }
+
+ var allInputContainers = Split(inputContainer);
+ foreach (var container in allInputContainers)
+ {
+ foreach (var profile in profileContainers)
+ {
+ if (string.Equals(profile, container, StringComparison.OrdinalIgnoreCase))
+ {
+ return !isNegativeList;
+ }
+ }
+ }
+
+ return isNegativeList;
+ }
+
+ /// <summary>
+ /// Splits and input string.
+ /// </summary>
+ /// <param name="input">The input string.</param>
+ /// <returns>The result of the operation.</returns>
+ public static string[] Split(string? input)
+ {
+ return input?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? [];
+ }
+}
diff --git a/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs
new file mode 100644
index 000000000..4a814f22a
--- /dev/null
+++ b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Linq;
+using MediaBrowser.Model.Configuration;
+
+namespace MediaBrowser.Model.Extensions;
+
+/// <summary>
+/// Extensions for <see cref="LibraryOptions"/>.
+/// </summary>
+public static class LibraryOptionsExtension
+{
+ /// <summary>
+ /// Get the custom tag delimiters.
+ /// </summary>
+ /// <param name="options">This LibraryOptions.</param>
+ /// <returns>CustomTagDelimiters in char[].</returns>
+ public static char[] GetCustomTagDelimiters(this LibraryOptions options)
+ {
+ ArgumentNullException.ThrowIfNull(options);
+
+ return options.CustomTagDelimiters.Select<string, char?>(x =>
+ {
+ var isChar = char.TryParse(x, out var c);
+ if (isChar)
+ {
+ return c;
+ }
+
+ return null;
+ }).Where(x => x is not null).Select(x => x!.Value).ToArray();
+ }
+}
diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs
index ec381d423..2085328dd 100644
--- a/MediaBrowser.Model/IO/IFileSystem.cs
+++ b/MediaBrowser.Model/IO/IFileSystem.cs
@@ -34,6 +34,13 @@ namespace MediaBrowser.Model.IO
string MakeAbsolutePath(string folderPath, string filePath);
/// <summary>
+ /// Moves a directory to a new location.
+ /// </summary>
+ /// <param name="source">Source directory.</param>
+ /// <param name="destination">Destination directory.</param>
+ void MoveDirectory(string source, string destination);
+
+ /// <summary>
/// Returns a <see cref="FileSystemMetadata" /> object for the specified file or directory path.
/// </summary>
/// <param name="path">A path to a file or directory.</param>
diff --git a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
index a832169c2..a355387b1 100644
--- a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
+++ b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
@@ -9,6 +9,9 @@ namespace MediaBrowser.Model.LiveTv
{
AllowHWTranscoding = true;
IgnoreDts = true;
+ AllowStreamSharing = true;
+ AllowFmp4TranscodingContainer = false;
+ FallbackMaxStreamingBitrate = 30000000;
}
public string Id { get; set; }
@@ -25,6 +28,12 @@ namespace MediaBrowser.Model.LiveTv
public bool AllowHWTranscoding { get; set; }
+ public bool AllowFmp4TranscodingContainer { get; set; }
+
+ public bool AllowStreamSharing { get; set; }
+
+ public int FallbackMaxStreamingBitrate { get; set; }
+
public bool EnableStreamLooping { get; set; }
public string Source { get; set; }
diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
index 24eab1a74..92f467eb0 100644
--- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
+++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
@@ -13,6 +13,7 @@ namespace MediaBrowser.Model.MediaInfo
{
EnableDirectPlay = true;
EnableDirectStream = true;
+ AlwaysBurnInSubtitleWhenTranscoding = false;
DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http };
}
@@ -40,6 +41,8 @@ namespace MediaBrowser.Model.MediaInfo
public bool EnableDirectStream { get; set; }
+ public bool AlwaysBurnInSubtitleWhenTranscoding { get; set; }
+
public IReadOnlyList<MediaProtocol> DirectPlayProtocols { get; set; }
}
}
diff --git a/MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs b/MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs
new file mode 100644
index 000000000..8c1f44de8
--- /dev/null
+++ b/MediaBrowser.Model/MediaSegments/MediaSegmentGenerationRequest.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace MediaBrowser.Model;
+
+/// <summary>
+/// Model containing the arguments for enumerating the requested media item.
+/// </summary>
+public record MediaSegmentGenerationRequest
+{
+ /// <summary>
+ /// Gets the Id to the BaseItem the segments should be extracted from.
+ /// </summary>
+ public Guid ItemId { get; init; }
+}
diff --git a/MediaBrowser.Model/Session/HardwareEncodingType.cs b/MediaBrowser.Model/Session/HardwareEncodingType.cs
deleted file mode 100644
index cf424fef5..000000000
--- a/MediaBrowser.Model/Session/HardwareEncodingType.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-namespace MediaBrowser.Model.Session
-{
- /// <summary>
- /// Enum HardwareEncodingType.
- /// </summary>
- public enum HardwareEncodingType
- {
- /// <summary>
- /// AMD AMF.
- /// </summary>
- AMF = 0,
-
- /// <summary>
- /// Intel Quick Sync Video.
- /// </summary>
- QSV = 1,
-
- /// <summary>
- /// NVIDIA NVENC.
- /// </summary>
- NVENC = 2,
-
- /// <summary>
- /// Video4Linux2 V4L2.
- /// </summary>
- V4L2M2M = 3,
-
- /// <summary>
- /// Video Acceleration API (VAAPI).
- /// </summary>
- VAAPI = 4,
-
- /// <summary>
- /// Video ToolBox.
- /// </summary>
- VideoToolBox = 5,
-
- /// <summary>
- /// Rockchip Media Process Platform (RKMPP).
- /// </summary>
- RKMPP = 6
- }
-}
diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs
index bbdf4536b..39c5ac8fa 100644
--- a/MediaBrowser.Model/Session/TranscodeReason.cs
+++ b/MediaBrowser.Model/Session/TranscodeReason.cs
@@ -18,6 +18,7 @@ namespace MediaBrowser.Model.Session
// Video Constraints
VideoProfileNotSupported = 1 << 6,
VideoRangeTypeNotSupported = 1 << 24,
+ VideoCodecTagNotSupported = 1 << 25,
VideoLevelNotSupported = 1 << 7,
VideoResolutionNotSupported = 1 << 8,
VideoBitDepthNotSupported = 1 << 9,
diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs
index 000cbd4c5..ae25267ac 100644
--- a/MediaBrowser.Model/Session/TranscodingInfo.cs
+++ b/MediaBrowser.Model/Session/TranscodingInfo.cs
@@ -1,34 +1,76 @@
#nullable disable
-#pragma warning disable CS1591
-namespace MediaBrowser.Model.Session
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Model.Session;
+
+/// <summary>
+/// Class holding information on a runnning transcode.
+/// </summary>
+public class TranscodingInfo
{
- public class TranscodingInfo
- {
- public string AudioCodec { get; set; }
+ /// <summary>
+ /// Gets or sets the thread count used for encoding.
+ /// </summary>
+ public string AudioCodec { get; set; }
- public string VideoCodec { get; set; }
+ /// <summary>
+ /// Gets or sets the thread count used for encoding.
+ /// </summary>
+ public string VideoCodec { get; set; }
- public string Container { get; set; }
+ /// <summary>
+ /// Gets or sets the thread count used for encoding.
+ /// </summary>
+ public string Container { get; set; }
- public bool IsVideoDirect { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether the video is passed through.
+ /// </summary>
+ public bool IsVideoDirect { get; set; }
- public bool IsAudioDirect { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether the audio is passed through.
+ /// </summary>
+ public bool IsAudioDirect { get; set; }
- public int? Bitrate { get; set; }
+ /// <summary>
+ /// Gets or sets the bitrate.
+ /// </summary>
+ public int? Bitrate { get; set; }
- public float? Framerate { get; set; }
+ /// <summary>
+ /// Gets or sets the framerate.
+ /// </summary>
+ public float? Framerate { get; set; }
- public double? CompletionPercentage { get; set; }
+ /// <summary>
+ /// Gets or sets the completion percentage.
+ /// </summary>
+ public double? CompletionPercentage { get; set; }
- public int? Width { get; set; }
+ /// <summary>
+ /// Gets or sets the video width.
+ /// </summary>
+ public int? Width { get; set; }
- public int? Height { get; set; }
+ /// <summary>
+ /// Gets or sets the video height.
+ /// </summary>
+ public int? Height { get; set; }
- public int? AudioChannels { get; set; }
+ /// <summary>
+ /// Gets or sets the audio channels.
+ /// </summary>
+ public int? AudioChannels { get; set; }
- public HardwareEncodingType? HardwareAccelerationType { get; set; }
+ /// <summary>
+ /// Gets or sets the hardware acceleration type.
+ /// </summary>
+ public HardwareAccelerationType? HardwareAccelerationType { get; set; }
- public TranscodeReason TranscodeReasons { get; set; }
- }
+ /// <summary>
+ /// Gets or sets the transcode reasons.
+ /// </summary>
+ public TranscodeReason TranscodeReasons { get; set; }
}