aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Model')
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs12
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs10
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs16
-rw-r--r--MediaBrowser.Model/Devices/DeviceInfo.cs119
-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
-rw-r--r--MediaBrowser.Model/Drawing/ImageFormatExtensions.cs8
-rw-r--r--MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs69
-rw-r--r--MediaBrowser.Model/Dto/DeviceInfoDto.cs83
-rw-r--r--MediaBrowser.Model/Dto/PlaylistDto.cs26
-rw-r--r--MediaBrowser.Model/Dto/SessionInfoDto.cs186
-rw-r--r--MediaBrowser.Model/Entities/HardwareAccelerationType.cs2
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs6
-rw-r--r--MediaBrowser.Model/Entities/MetadataProvider.cs2
-rw-r--r--MediaBrowser.Model/Entities/ProviderIdsExtensions.cs6
-rw-r--r--MediaBrowser.Model/Extensions/ContainerHelper.cs15
-rw-r--r--MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs34
-rw-r--r--MediaBrowser.Model/Globalization/ILocalizationManager.cs2
-rw-r--r--MediaBrowser.Model/IO/IFileSystem.cs4
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj6
-rw-r--r--MediaBrowser.Model/Net/MimeTypes.cs4
-rw-r--r--MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs2
-rw-r--r--MediaBrowser.Model/Plugins/PluginStatus.cs7
-rw-r--r--MediaBrowser.Model/Session/TranscodingInfo.cs2
-rw-r--r--MediaBrowser.Model/System/PublicSystemInfo.cs2
-rw-r--r--MediaBrowser.Model/Tasks/TaskTriggerInfo.cs22
-rw-r--r--MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs28
32 files changed, 722 insertions, 194 deletions
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index d67a2479f..2720c0bdf 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -46,6 +46,8 @@ public class EncodingOptions
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;
@@ -235,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 04283cc9e..590b74304 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -2,19 +2,20 @@
using System;
using System.ComponentModel;
+using System.Linq;
namespace MediaBrowser.Model.Configuration
{
public class LibraryOptions
{
- private static readonly char[] _defaultTagDelimiters = ['/', '|', ';', '\\'];
+ private static readonly string[] _defaultTagDelimiters = ["/", "|", ";", "\\"];
public LibraryOptions()
{
TypeOptions = Array.Empty<TypeOptions>();
DisabledSubtitleFetchers = Array.Empty<string>();
DisabledMediaSegmentProviders = Array.Empty<string>();
- MediaSegmentProvideOrder = Array.Empty<string>();
+ MediaSegmentProviderOrder = Array.Empty<string>();
SubtitleFetcherOrder = Array.Empty<string>();
DisabledLocalMetadataReaders = Array.Empty<string>();
DisabledLyricFetchers = Array.Empty<string>();
@@ -98,7 +99,7 @@ namespace MediaBrowser.Model.Configuration
public string[] DisabledMediaSegmentProviders { get; set; }
- public string[] MediaSegmentProvideOrder { get; set; }
+ public string[] MediaSegmentProviderOrder { get; set; }
public bool SkipSubtitlesIfEmbeddedSubtitlesPresent { get; set; }
@@ -126,8 +127,7 @@ namespace MediaBrowser.Model.Configuration
[DefaultValue(false)]
public bool UseCustomTagDelimiters { get; set; }
- [DefaultValue(typeof(LibraryOptions), nameof(_defaultTagDelimiters))]
- public char[] CustomTagDelimiters { get; set; }
+ public string[] CustomTagDelimiters { get; set; }
public string[] DelimiterWhitelist { get; set; }
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 5ad588200..693bf90e7 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -83,9 +83,9 @@ public class ServerConfiguration : BaseApplicationConfiguration
public bool QuickConnectAvailable { get; set; } = true;
/// <summary>
- /// Gets or sets a value indicating whether [enable case sensitive item ids].
+ /// Gets or sets a value indicating whether [enable case-sensitive item ids].
/// </summary>
- /// <value><c>true</c> if [enable case sensitive item ids]; otherwise, <c>false</c>.</value>
+ /// <value><c>true</c> if [enable case-sensitive item ids]; otherwise, <c>false</c>.</value>
public bool EnableCaseSensitiveItemIds { get; set; } = true;
public bool DisableLiveTvChannelUserDataName { get; set; } = true;
@@ -244,17 +244,12 @@ public class ServerConfiguration : BaseApplicationConfiguration
public int LibraryMetadataRefreshConcurrency { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether older plugins should automatically be deleted from the plugin folder.
- /// </summary>
- public bool RemoveOldPlugins { get; set; }
-
- /// <summary>
/// Gets or sets a value indicating whether clients should be allowed to upload logs.
/// </summary>
public bool AllowClientLogUpload { get; set; } = true;
/// <summary>
- /// Gets or sets the dummy chapter duration in seconds, use 0 (zero) or less to disable generation alltogether.
+ /// Gets or sets the dummy chapter duration in seconds, use 0 (zero) or less to disable generation altogether.
/// </summary>
/// <value>The dummy chapters duration.</value>
public int DummyChapterDuration { get; set; }
@@ -281,4 +276,9 @@ public class ServerConfiguration : BaseApplicationConfiguration
/// </summary>
/// <value>The trickplay options.</value>
public TrickplayOptions TrickplayOptions { get; set; } = new TrickplayOptions();
+
+ /// <summary>
+ /// Gets or sets a value indicating whether old authorization methods are allowed.
+ /// </summary>
+ public bool EnableLegacyAuthorization { get; set; } = true;
}
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/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index af0787990..1b046f54e 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 f68957622..995b7633a 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 438df3441..553ccfc64 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 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 bf122dcc7..1ed493708 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 3be686088..e44152213 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 5a9fa22ae..5797d4250 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")]
diff --git a/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs b/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs
index 1c60ba460..53b9b1fad 100644
--- a/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs
+++ b/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs
@@ -17,12 +17,12 @@ public static class ImageFormatExtensions
public static string GetMimeType(this ImageFormat format)
=> format switch
{
- ImageFormat.Bmp => "image/bmp",
+ ImageFormat.Bmp => MediaTypeNames.Image.Bmp,
ImageFormat.Gif => MediaTypeNames.Image.Gif,
ImageFormat.Jpg => MediaTypeNames.Image.Jpeg,
- ImageFormat.Png => "image/png",
- ImageFormat.Webp => "image/webp",
- ImageFormat.Svg => "image/svg+xml",
+ ImageFormat.Png => MediaTypeNames.Image.Png,
+ ImageFormat.Webp => MediaTypeNames.Image.Webp,
+ ImageFormat.Svg => MediaTypeNames.Image.Svg,
_ => throw new InvalidEnumArgumentException(nameof(format), (int)format, typeof(ImageFormat))
};
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/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..d727cd874
--- /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 playlist 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/HardwareAccelerationType.cs b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs
index 198a2e00f..ece18ec3e 100644
--- a/MediaBrowser.Model/Entities/HardwareAccelerationType.cs
+++ b/MediaBrowser.Model/Entities/HardwareAccelerationType.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Entities;
public enum HardwareAccelerationType
{
/// <summary>
- /// Software accelleration.
+ /// Software acceleration.
/// </summary>
none = 0,
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 85c1f797b..218a22aa2 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -383,7 +383,7 @@ namespace MediaBrowser.Model.Entities
attributes.Add(string.IsNullOrEmpty(LocalizedUndefined) ? "Und" : LocalizedUndefined);
}
- if (IsHearingImpaired)
+ if (IsHearingImpaired == true)
{
attributes.Add(string.IsNullOrEmpty(LocalizedHearingImpaired) ? "Hearing Impaired" : LocalizedHearingImpaired);
}
@@ -500,7 +500,7 @@ namespace MediaBrowser.Model.Entities
/// Gets or sets a value indicating whether this instance is for the hearing impaired.
/// </summary>
/// <value><c>true</c> if this instance is for the hearing impaired; otherwise, <c>false</c>.</value>
- public bool IsHearingImpaired { get; set; }
+ public bool? IsHearingImpaired { get; set; }
/// <summary>
/// Gets or sets the height.
@@ -537,7 +537,7 @@ namespace MediaBrowser.Model.Entities
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.
+ // This is probably due to a library compatibility issue.
// See https://github.com/jellyfin/jellyfin/pull/12603#discussion_r1748044018 for more info.
return AverageFrameRate < 1000 ? AverageFrameRate : RealFrameRate;
}
diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs
index bd8db9941..dcc4ae88c 100644
--- a/MediaBrowser.Model/Entities/MetadataProvider.cs
+++ b/MediaBrowser.Model/Entities/MetadataProvider.cs
@@ -27,7 +27,7 @@ namespace MediaBrowser.Model.Entities
Tvdb = 4,
/// <summary>
- /// The tvcom providerd.
+ /// The tvcom provider.
/// </summary>
Tvcom = 5,
diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
index 479ec7712..385a86d31 100644
--- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
+++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
@@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Entities;
public static class ProviderIdsExtensions
{
/// <summary>
- /// Case insensitive dictionary of <see cref="MetadataProvider"/> string representation.
+ /// Case-insensitive dictionary of <see cref="MetadataProvider"/> string representation.
/// </summary>
private static readonly Dictionary<string, string> _metadataProviderEnumDictionary =
Enum.GetValues<MetadataProvider>()
@@ -107,7 +107,7 @@ public static class ProviderIdsExtensions
/// <param name="instance">The instance.</param>
/// <param name="name">The name, this should not contain a '=' character.</param>
/// <param name="value">The value.</param>
- /// <remarks>Due to how deserialization from the database works the name can not contain '='.</remarks>
+ /// <remarks>Due to how deserialization from the database works the name cannot contain '='.</remarks>
/// <returns><c>true</c> if the provider id got set successfully; otherwise, <c>false</c>.</returns>
public static bool TrySetProviderId(this IHasProviderIds instance, string? name, string? value)
{
@@ -153,7 +153,7 @@ public static class ProviderIdsExtensions
/// <param name="instance">The instance.</param>
/// <param name="name">The name, this should not contain a '=' character.</param>
/// <param name="value">The value.</param>
- /// <remarks>Due to how deserialization from the database works the name can not contain '='.</remarks>
+ /// <remarks>Due to how deserialization from the database works the name cannot contain '='.</remarks>
public static void SetProviderId(this IHasProviderIds instance, string name, string value)
{
ArgumentNullException.ThrowIfNull(instance);
diff --git a/MediaBrowser.Model/Extensions/ContainerHelper.cs b/MediaBrowser.Model/Extensions/ContainerHelper.cs
index c86328ba6..39e5358ba 100644
--- a/MediaBrowser.Model/Extensions/ContainerHelper.cs
+++ b/MediaBrowser.Model/Extensions/ContainerHelper.cs
@@ -14,7 +14,8 @@ public static class ContainerHelper
/// 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>
+ /// If the parameter begins with the <c>-</c> character, the operation is reversed.
+ /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</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)
@@ -34,7 +35,8 @@ public static class ContainerHelper
/// 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>
+ /// If the parameter begins with the <c>-</c> character, the operation is reversed.
+ /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</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)
@@ -53,7 +55,8 @@ public static class ContainerHelper
/// 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="profileContainers">The comma-delimited string being searched.
+ /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</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>
@@ -71,7 +74,8 @@ public static class ContainerHelper
/// 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="profileContainers">The comma-delimited string being searched.
+ /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</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>
@@ -106,7 +110,8 @@ public static class ContainerHelper
/// 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="profileContainers">The profile containers being matched searched.
+ /// If the parameter is empty or null, all containers in <paramref name="inputContainer"/> will be accepted.</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>
diff --git a/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs
new file mode 100644
index 000000000..b088cfb53
--- /dev/null
+++ b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs
@@ -0,0 +1,34 @@
+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);
+
+ var delimiterList = 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).ToList();
+ delimiterList.Add('\0');
+ return delimiterList.ToArray();
+ }
+}
diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
index 02a29e7fa..20deaa505 100644
--- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs
+++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
@@ -52,7 +52,7 @@ namespace MediaBrowser.Model.Globalization
/// <summary>
/// Gets the localization options.
/// </summary>
- /// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns>
+ /// <returns><see cref="IEnumerable{LocalizationOption}" />.</returns>
IEnumerable<LocalizationOption> GetLocalizationOptions();
/// <summary>
diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs
index 2085328dd..229368d00 100644
--- a/MediaBrowser.Model/IO/IFileSystem.cs
+++ b/MediaBrowser.Model/IO/IFileSystem.cs
@@ -145,7 +145,7 @@ namespace MediaBrowser.Model.IO
/// Gets the directories.
/// </summary>
/// <param name="path">The path.</param>
- /// <param name="recursive">If set to <c>true</c> also searches in subdirectiories.</param>
+ /// <param name="recursive">If set to <c>true</c> also searches in subdirectories.</param>
/// <returns>All found directories.</returns>
IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false);
@@ -153,7 +153,7 @@ namespace MediaBrowser.Model.IO
/// Gets the files.
/// </summary>
/// <param name="path">The path in which to search.</param>
- /// <param name="recursive">If set to <c>true</c> also searches in subdirectiories.</param>
+ /// <param name="recursive">If set to <c>true</c> also searches in subdirectories.</param>
/// <returns>All found files.</returns>
IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false);
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 9489fe190..e9dab6bc8 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -8,13 +8,13 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
- <VersionPrefix>10.10.0</VersionPrefix>
+ <VersionPrefix>10.11.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@@ -35,7 +35,7 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
-
+
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="MimeTypes">
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index 5d65b0f9b..de087d0e7 100644
--- a/MediaBrowser.Model/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using System.Net.Mime;
using Jellyfin.Extensions;
namespace MediaBrowser.Model.Net
@@ -125,6 +126,7 @@ namespace MediaBrowser.Model.Net
new("audio/vorbis", ".vorbis"),
new("audio/x-ape", ".ape"),
new("audio/xsp", ".xsp"),
+ new("audio/x-aac", ".aac"),
new("audio/x-wavpack", ".wv"),
// Type image
@@ -143,7 +145,7 @@ namespace MediaBrowser.Model.Net
new("video/x-matroska", ".mkv"),
}.ToFrozenDictionary(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase);
- public static string GetMimeType(string path) => GetMimeType(path, "application/octet-stream");
+ public static string GetMimeType(string path) => GetMimeType(path, MediaTypeNames.Application.Octet);
/// <summary>
/// Gets the type of the MIME.
diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs
index ec54b1afd..98f7c6ce1 100644
--- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs
+++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs
@@ -38,5 +38,5 @@ public class PlaylistCreationRequest
/// <summary>
/// Gets or sets a value indicating whether the playlist is public.
/// </summary>
- public bool? Public { get; set; } = true;
+ public bool? Public { get; set; } = false;
}
diff --git a/MediaBrowser.Model/Plugins/PluginStatus.cs b/MediaBrowser.Model/Plugins/PluginStatus.cs
index bd420d7b4..9c7a8f0c2 100644
--- a/MediaBrowser.Model/Plugins/PluginStatus.cs
+++ b/MediaBrowser.Model/Plugins/PluginStatus.cs
@@ -34,7 +34,12 @@ namespace MediaBrowser.Model.Plugins
Malfunctioned = -3,
/// <summary>
- /// This plugin has been superceded by another version.
+ /// This plugin has been superseded by another version.
+ /// </summary>
+ Superseded = -4,
+
+ /// <summary>
+ /// [DEPRECATED] See Superseded.
/// </summary>
Superceded = -4,
diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs
index ae25267ac..11e83844b 100644
--- a/MediaBrowser.Model/Session/TranscodingInfo.cs
+++ b/MediaBrowser.Model/Session/TranscodingInfo.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Session;
/// <summary>
-/// Class holding information on a runnning transcode.
+/// Class holding information on a running transcode.
/// </summary>
public class TranscodingInfo
{
diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs
index 31a895642..c26cfb667 100644
--- a/MediaBrowser.Model/System/PublicSystemInfo.cs
+++ b/MediaBrowser.Model/System/PublicSystemInfo.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Model.System
/// Gets or sets a value indicating whether the startup wizard is completed.
/// </summary>
/// <remarks>
- /// Nullable for OpenAPI specification only to retain backwards compatibility in apiclients.
+ /// Nullable for OpenAPI specification only to retain backwards compatibility in api clients.
/// </remarks>
/// <value>The startup completion status.</value>]
public bool? StartupWizardCompleted { get; set; }
diff --git a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
index 63709557d..186c0aed3 100644
--- a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
+++ b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
@@ -9,30 +9,10 @@ namespace MediaBrowser.Model.Tasks
public class TaskTriggerInfo
{
/// <summary>
- /// The daily trigger.
- /// </summary>
- public const string TriggerDaily = "DailyTrigger";
-
- /// <summary>
- /// The weekly trigger.
- /// </summary>
- public const string TriggerWeekly = "WeeklyTrigger";
-
- /// <summary>
- /// The interval trigger.
- /// </summary>
- public const string TriggerInterval = "IntervalTrigger";
-
- /// <summary>
- /// The startup trigger.
- /// </summary>
- public const string TriggerStartup = "StartupTrigger";
-
- /// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
- public string Type { get; set; }
+ public TaskTriggerInfoType Type { get; set; }
/// <summary>
/// Gets or sets the time of day.
diff --git a/MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs b/MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs
new file mode 100644
index 000000000..b596cf580
--- /dev/null
+++ b/MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs
@@ -0,0 +1,28 @@
+namespace MediaBrowser.Model.Tasks
+{
+ /// <summary>
+ /// Enum TaskTriggerInfoType.
+ /// </summary>
+ public enum TaskTriggerInfoType
+ {
+ /// <summary>
+ /// The daily trigger.
+ /// </summary>
+ DailyTrigger,
+
+ /// <summary>
+ /// The weekly trigger.
+ /// </summary>
+ WeeklyTrigger,
+
+ /// <summary>
+ /// The interval trigger.
+ /// </summary>
+ IntervalTrigger,
+
+ /// <summary>
+ /// The startup trigger.
+ /// </summary>
+ StartupTrigger
+ }
+}