aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model/Dlna
diff options
context:
space:
mode:
authorMarc Brooks <IDisposable@gmail.com>2025-02-03 19:48:59 -0600
committerGitHub <noreply@github.com>2025-02-03 19:48:59 -0600
commite8cbcde02ebd930a5eeb6c95e0875a9e30acb3e8 (patch)
tree2ecd43f232012c8f037f4cd6fee4168e46d01aa3 /MediaBrowser.Model/Dlna
parent6dc61a430ba3a8480399309f277e5debfd6403ba (diff)
parentd376b5fbc7cf3ae7440a606a9e885d70605956bd (diff)
Merge branch 'master' into sort-nfo-data
Diffstat (limited to 'MediaBrowser.Model/Dlna')
-rw-r--r--MediaBrowser.Model/Dlna/ConditionProcessor.cs4
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfile.cs2
-rw-r--r--MediaBrowser.Model/Dlna/DirectPlayProfile.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ResolutionNormalizer.cs69
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs126
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs11
-rw-r--r--MediaBrowser.Model/Dlna/TranscodingProfile.cs29
7 files changed, 163 insertions, 80 deletions
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index af0787990d..1b046f54ea 100644
--- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -25,8 +25,8 @@ namespace MediaBrowser.Model.Dlna
/// <param name="videoFramerate">The framerate.</param>
/// <param name="packetLength">The packet length.</param>
/// <param name="timestamp">The <see cref="TransportStreamTimestamp"/>.</param>
- /// <param name="isAnamorphic">A value indicating whether tthe video is anamorphic.</param>
- /// <param name="isInterlaced">A value indicating whether tthe video is interlaced.</param>
+ /// <param name="isAnamorphic">A value indicating whether the video is anamorphic.</param>
+ /// <param name="isInterlaced">A value indicating whether the video is interlaced.</param>
/// <param name="refFrames">The reference frames.</param>
/// <param name="numVideoStreams">The number of video streams.</param>
/// <param name="numAudioStreams">The number of audio streams.</param>
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index f689576222..995b7633a9 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -22,7 +22,7 @@ public class DeviceProfile
/// <summary>
/// Gets or sets the unique internal identifier.
/// </summary>
- public Guid Id { get; set; }
+ public Guid? Id { get; set; }
/// <summary>
/// Gets or sets the maximum allowed bitrate for all streamed content.
diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
index 438df34415..553ccfc64b 100644
--- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
+++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
@@ -59,7 +59,7 @@ public class DirectPlayProfile
/// <returns>True if supported.</returns>
public bool SupportsAudioCodec(string? codec)
{
- // Video profiles can have audio codec restrictions too, therefore incude Video as valid type.
+ // Video profiles can have audio codec restrictions too, therefore include Video as valid type.
return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerHelper.ContainsContainer(AudioCodec, codec);
}
}
diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
index 5d7daa81aa..1a636b2403 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 bf122dcc7f..1ed493708a 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -30,7 +30,7 @@ namespace MediaBrowser.Model.Dlna
private readonly ITranscoderSupport _transcoderSupport;
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"];
+ private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dts", "truehd"];
/// <summary>
/// Initializes a new instance of the <see cref="StreamBuilder"/> class.
@@ -65,7 +65,7 @@ namespace MediaBrowser.Model.Dlna
if (streamInfo is not null)
{
streamInfo.DeviceId = options.DeviceId;
- streamInfo.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture);
+ streamInfo.DeviceProfileId = options.Profile.Id?.ToString("N", CultureInfo.InvariantCulture);
streams.Add(streamInfo);
}
}
@@ -208,6 +208,14 @@ namespace MediaBrowser.Model.Dlna
var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate);
playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
+
+ // Pure audio transcoding does not support comma separated list of transcoding codec at the moment.
+ // So just use the AudioCodec as is would be safe enough as the _transcoderSupport.CanEncodeToAudioCodec
+ // would fail so this profile will not even be picked up.
+ if (playlistItem.AudioCodecs.Count == 0 && !string.IsNullOrWhiteSpace(transcodingProfile.AudioCodec))
+ {
+ playlistItem.AudioCodecs = [transcodingProfile.AudioCodec];
+ }
}
playlistItem.TranscodeReasons = transcodeReasons;
@@ -240,7 +248,7 @@ namespace MediaBrowser.Model.Dlna
foreach (var stream in streams)
{
stream.DeviceId = options.DeviceId;
- stream.DeviceProfileId = options.Profile.Id.ToString("N", CultureInfo.InvariantCulture);
+ stream.DeviceProfileId = options.Profile.Id?.ToString("N", CultureInfo.InvariantCulture);
}
return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0);
@@ -389,9 +397,10 @@ namespace MediaBrowser.Model.Dlna
/// <param name="type">The <see cref="DlnaProfileType"/>.</param>
/// <param name="playProfile">The <see cref="DirectPlayProfile"/> object to get the video stream from.</param>
/// <returns>The normalized input container.</returns>
- public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null)
+ public static string? NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null)
{
- if (profile is null || !inputContainer.Contains(',', StringComparison.OrdinalIgnoreCase))
+ // 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 inputContainer;
}
@@ -639,7 +648,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;
@@ -767,20 +777,7 @@ namespace MediaBrowser.Model.Dlna
if (subtitleStream is not null)
{
var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
-
- if (options.AlwaysBurnInSubtitleWhenTranscoding && (playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
- {
- playlistItem.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
- foreach (SubtitleProfile profile in options.Profile.SubtitleProfiles)
- {
- profile.Method = SubtitleDeliveryMethod.Encode;
- }
- }
- else
- {
- playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
- }
-
+ playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
playlistItem.SubtitleFormat = subtitleProfile.Format;
playlistItem.SubtitleCodecs = [subtitleProfile.Format];
}
@@ -865,18 +862,37 @@ namespace MediaBrowser.Model.Dlna
if (options.AllowAudioStreamCopy)
{
- if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec))
+ // For Audio stream, we prefer the audio codec that can be directly copied, then the codec that can otherwise satisfies
+ // the transcoding conditions, then the one does not satisfy the transcoding conditions.
+ // For example: A client can support both aac and flac, but flac only supports 2 channels while aac supports 6.
+ // When the source audio is 6 channel flac, we should transcode to 6 channel aac, instead of down-mix to 2 channel flac.
+ var transcodingAudioCodecs = ContainerHelper.Split(transcodingProfile.AudioCodec);
+
+ foreach (var transcodingAudioCodec in transcodingAudioCodecs)
{
var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.VideoAudio &&
- i.ContainsAnyCodec(audioCodec, container) &&
+ i.ContainsAnyCodec(transcodingAudioCodec, 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;
+
+ var rankAudio = 3;
+
+ if (conditionsSatisfied)
+ {
+ rankAudio = string.Equals(transcodingAudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) ? 1 : 2;
+ }
+
+ rank.Audio = Math.Min(rank.Audio, rankAudio);
+
+ if (rank.Audio == 1)
+ {
+ break;
+ }
}
}
@@ -966,9 +982,28 @@ namespace MediaBrowser.Model.Dlna
var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerHelper.ContainsContainer(audioCodecs, false, stream.Codec)).FirstOrDefault();
- var directAudioStream = audioStreamWithSupportedCodec?.Channels is not null && audioStreamWithSupportedCodec.Channels.Value <= (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue) ? audioStreamWithSupportedCodec : null;
+ var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && audioStreamWithSupportedCodec.Channels > (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue);
+
+ var directAudioStreamSatisfied = audioStreamWithSupportedCodec is not null && !channelsExceedsLimit
+ && options.Profile.CodecProfiles
+ .Where(i => i.Type == CodecType.VideoAudio
+ && i.ContainsAnyCodec(audioStreamWithSupportedCodec.Codec, container)
+ && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioStreamWithSupportedCodec.Channels, audioStreamWithSupportedCodec.BitRate, audioStreamWithSupportedCodec.SampleRate, audioStreamWithSupportedCodec.BitDepth, audioStreamWithSupportedCodec.Profile, false)))
+ .Select(i => i.Conditions.All(condition =>
+ {
+ var satisfied = ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioStreamWithSupportedCodec.Channels, audioStreamWithSupportedCodec.BitRate, audioStreamWithSupportedCodec.SampleRate, audioStreamWithSupportedCodec.BitDepth, audioStreamWithSupportedCodec.Profile, false);
+ if (!satisfied)
+ {
+ playlistItem.TranscodeReasons |= GetTranscodeReasonForFailedCondition(condition);
+ }
- var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && directAudioStream is null;
+ return satisfied;
+ }))
+ .All(satisfied => satisfied);
+
+ directAudioStreamSatisfied = directAudioStreamSatisfied && !playlistItem.TranscodeReasons.HasFlag(TranscodeReason.ContainerBitrateExceedsLimit);
+
+ var directAudioStream = directAudioStreamSatisfied ? audioStreamWithSupportedCodec : null;
if (channelsExceedsLimit && playlistItem.TargetAudioStream is not null)
{
@@ -1090,12 +1125,12 @@ namespace MediaBrowser.Model.Dlna
_logger.LogDebug(
"Transcode Result for Profile: {Profile}, Path: {Path}, PlayMethod: {PlayMethod}, AudioStreamIndex: {AudioStreamIndex}, SubtitleStreamIndex: {SubtitleStreamIndex}, Reasons: {TranscodeReason}",
- options.Profile?.Name ?? "Anonymous Profile",
+ options.Profile.Name ?? "Anonymous Profile",
item.Path ?? "Unknown path",
- playlistItem?.PlayMethod,
+ playlistItem.PlayMethod,
audioStream?.Index,
- playlistItem?.SubtitleStreamIndex,
- playlistItem?.TranscodeReasons);
+ playlistItem.SubtitleStreamIndex,
+ playlistItem.TranscodeReasons);
}
private static int GetDefaultAudioBitrate(string? audioCodec, int? audioChannels)
@@ -2216,7 +2251,7 @@ namespace MediaBrowser.Model.Dlna
}
}
- private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
+ private static bool IsAudioContainerSupported(DirectPlayProfile profile, MediaSourceInfo item)
{
// Check container type
if (!profile.SupportsContainer(item.Container))
@@ -2224,6 +2259,20 @@ namespace MediaBrowser.Model.Dlna
return false;
}
+ // Never direct play audio in matroska when the device only declare support for webm.
+ // The first check is not enough because mkv is assumed can be webm.
+ // See https://github.com/jellyfin/jellyfin/issues/13344
+ return !ContainerHelper.ContainsContainer("mkv", item.Container)
+ || profile.SupportsContainer("mkv");
+ }
+
+ private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
+ {
+ if (!IsAudioContainerSupported(profile, item))
+ {
+ return false;
+ }
+
// Check audio codec
string? audioCodec = audioStream?.Codec;
if (!profile.SupportsAudioCodec(audioCodec))
@@ -2238,19 +2287,16 @@ namespace MediaBrowser.Model.Dlna
{
// Check container type, this should NOT be supported
// If the container is supported, the file should be directly played
- if (!profile.SupportsContainer(item.Container))
+ if (IsAudioContainerSupported(profile, item))
{
- // Check audio codec, we cannot use the SupportsAudioCodec here
- // Because that one assumes empty container supports all codec, which is just useless
- string? audioCodec = audioStream?.Codec;
- if (string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
+ return false;
}
- return false;
+ // Check audio codec, we cannot use the SupportsAudioCodec here
+ // Because that one assumes empty container supports all codec, which is just useless
+ string? audioCodec = audioStream?.Codec;
+ return string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase);
}
private int GetRank(ref TranscodeReason a, TranscodeReason[] rankings)
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 3be6860880..e441522136 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -271,6 +271,11 @@ public class StreamInfo
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; }
+
+ /// <summary>
/// Gets a value indicating whether the stream is direct.
/// </summary>
public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay)
@@ -953,7 +958,7 @@ public class StreamInfo
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("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));
@@ -974,7 +979,7 @@ public class StreamInfo
}
list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
- list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
+ list.Add(new NameValuePair("ApiKey", accessToken ?? string.Empty));
string? liveStreamId = item.MediaSource?.LiveStreamId;
list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
@@ -1184,7 +1189,7 @@ public class StreamInfo
if (!string.IsNullOrEmpty(accessToken))
{
- info.Url += "?api_key=" + accessToken;
+ info.Url += "?ApiKey=" + accessToken;
}
info.IsExternalUrl = false;
diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
index 5a9fa22ae4..5797d42506 100644
--- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs
+++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
@@ -1,3 +1,4 @@
+using System;
using System.ComponentModel;
using System.Xml.Serialization;
using Jellyfin.Data.Enums;
@@ -6,6 +7,7 @@ namespace MediaBrowser.Model.Dlna;
/// <summary>
/// A class for transcoding profile information.
+/// Note for client developers: Conditions defined in <see cref="CodecProfile"/> has higher priority and can override values defined here.
/// </summary>
public class TranscodingProfile
{
@@ -18,6 +20,33 @@ public class TranscodingProfile
}
/// <summary>
+ /// Initializes a new instance of the <see cref="TranscodingProfile" /> class copying the values from another instance.
+ /// </summary>
+ /// <param name="other">Another instance of <see cref="TranscodingProfile" /> to be copied.</param>
+ public TranscodingProfile(TranscodingProfile other)
+ {
+ ArgumentNullException.ThrowIfNull(other);
+
+ Container = other.Container;
+ Type = other.Type;
+ VideoCodec = other.VideoCodec;
+ AudioCodec = other.AudioCodec;
+ Protocol = other.Protocol;
+ EstimateContentLength = other.EstimateContentLength;
+ EnableMpegtsM2TsMode = other.EnableMpegtsM2TsMode;
+ TranscodeSeekInfo = other.TranscodeSeekInfo;
+ CopyTimestamps = other.CopyTimestamps;
+ Context = other.Context;
+ EnableSubtitlesInManifest = other.EnableSubtitlesInManifest;
+ MaxAudioChannels = other.MaxAudioChannels;
+ MinSegments = other.MinSegments;
+ SegmentLength = other.SegmentLength;
+ BreakOnNonKeyFrames = other.BreakOnNonKeyFrames;
+ Conditions = other.Conditions;
+ EnableAudioVbrEncoding = other.EnableAudioVbrEncoding;
+ }
+
+ /// <summary>
/// Gets or sets the container.
/// </summary>
[XmlAttribute("container")]