aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Model')
-rw-r--r--MediaBrowser.Model/Activity/IActivityManager.cs2
-rw-r--r--MediaBrowser.Model/Branding/BrandingOptions.cs7
-rw-r--r--MediaBrowser.Model/Branding/BrandingOptionsDto.cs25
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs20
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ConditionProcessor.cs17
-rw-r--r--MediaBrowser.Model/Dlna/DirectPlayProfile.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ProfileConditionValue.cs3
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs376
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs319
-rw-r--r--MediaBrowser.Model/Dlna/TranscodingProfile.cs29
-rw-r--r--MediaBrowser.Model/Drawing/ImageFormatExtensions.cs8
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs6
-rw-r--r--MediaBrowser.Model/Dto/BaseItemPerson.cs2
-rw-r--r--MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs4
-rw-r--r--MediaBrowser.Model/Dto/DisplayPreferencesDto.cs2
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs14
-rw-r--r--MediaBrowser.Model/Dto/MetadataEditorInfo.cs70
-rw-r--r--MediaBrowser.Model/Dto/SessionInfoDto.cs2
-rw-r--r--MediaBrowser.Model/Entities/HardwareAccelerationType.cs2
-rw-r--r--MediaBrowser.Model/Entities/MediaAttachment.cs80
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs47
-rw-r--r--MediaBrowser.Model/Entities/MetadataProvider.cs9
-rw-r--r--MediaBrowser.Model/Entities/ParentalRating.cs55
-rw-r--r--MediaBrowser.Model/Entities/ParentalRatingEntry.cs22
-rw-r--r--MediaBrowser.Model/Entities/ParentalRatingScore.cs32
-rw-r--r--MediaBrowser.Model/Entities/ParentalRatingSystem.cs28
-rw-r--r--MediaBrowser.Model/Entities/ProviderIdsExtensions.cs6
-rw-r--r--MediaBrowser.Model/Extensions/ContainerHelper.cs19
-rw-r--r--MediaBrowser.Model/Globalization/ILocalizationManager.cs110
-rw-r--r--MediaBrowser.Model/IO/AsyncFile.cs8
-rw-r--r--MediaBrowser.Model/IO/IFileSystem.cs32
-rw-r--r--MediaBrowser.Model/Library/UserViewQuery.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs1
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/TunerHostInfo.cs3
-rw-r--r--MediaBrowser.Model/Lyrics/LyricLine.cs11
-rw-r--r--MediaBrowser.Model/Lyrics/LyricLineCue.cs35
-rw-r--r--MediaBrowser.Model/Lyrics/LyricSearchRequest.cs7
-rw-r--r--MediaBrowser.Model/MediaInfo/AudioIndexSource.cs30
-rw-r--r--MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs4
-rw-r--r--MediaBrowser.Model/Net/MimeTypes.cs3
-rw-r--r--MediaBrowser.Model/Plugins/PluginStatus.cs7
-rw-r--r--MediaBrowser.Model/Providers/ExternalIdInfo.cs14
-rw-r--r--MediaBrowser.Model/Providers/ExternalIdMediaType.cs7
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs79
-rw-r--r--MediaBrowser.Model/Querying/LatestItemsQuery.cs2
-rw-r--r--MediaBrowser.Model/Querying/NextUpQuery.cs115
-rw-r--r--MediaBrowser.Model/Search/SearchHint.cs2
-rw-r--r--MediaBrowser.Model/Session/TranscodeReason.cs1
-rw-r--r--MediaBrowser.Model/Session/TranscodingInfo.cs2
-rw-r--r--MediaBrowser.Model/SyncPlay/GroupUpdate.cs17
-rw-r--r--MediaBrowser.Model/SyncPlay/GroupUpdateOfT.cs31
-rw-r--r--MediaBrowser.Model/SyncPlay/GroupUpdateType.cs10
-rw-r--r--MediaBrowser.Model/SyncPlay/SyncPlayGroupDoesNotExistUpdate.cs21
-rw-r--r--MediaBrowser.Model/SyncPlay/SyncPlayGroupJoinedUpdate.cs21
-rw-r--r--MediaBrowser.Model/SyncPlay/SyncPlayGroupLeftUpdate.cs21
-rw-r--r--MediaBrowser.Model/SyncPlay/SyncPlayLibraryAccessDeniedUpdate.cs21
-rw-r--r--MediaBrowser.Model/SyncPlay/SyncPlayNotInGroupUpdate.cs21
-rw-r--r--MediaBrowser.Model/SyncPlay/SyncPlayPlayQueueUpdate.cs21
-rw-r--r--MediaBrowser.Model/SyncPlay/SyncPlayStateUpdate.cs21
-rw-r--r--MediaBrowser.Model/SyncPlay/SyncPlayUserJoinedUpdate.cs21
-rw-r--r--MediaBrowser.Model/SyncPlay/SyncPlayUserLeftUpdate.cs21
-rw-r--r--MediaBrowser.Model/System/FolderStorageInfo.cs32
-rw-r--r--MediaBrowser.Model/System/LibraryStorageInfo.cs25
-rw-r--r--MediaBrowser.Model/System/PublicSystemInfo.cs2
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs254
-rw-r--r--MediaBrowser.Model/System/SystemStorageInfo.cs56
-rw-r--r--MediaBrowser.Model/System/WakeOnLanInfo.cs47
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs5
71 files changed, 1577 insertions, 780 deletions
diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs
index 28073fb8d..95aa567ad 100644
--- a/MediaBrowser.Model/Activity/IActivityManager.cs
+++ b/MediaBrowser.Model/Activity/IActivityManager.cs
@@ -2,9 +2,9 @@
using System;
using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Events;
using Jellyfin.Data.Queries;
+using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Model.Activity
diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs
index c6580598b..5ec6b0dd4 100644
--- a/MediaBrowser.Model/Branding/BrandingOptions.cs
+++ b/MediaBrowser.Model/Branding/BrandingOptions.cs
@@ -1,5 +1,3 @@
-using System.Text.Json.Serialization;
-
namespace MediaBrowser.Model.Branding;
/// <summary>
@@ -27,10 +25,5 @@ public class BrandingOptions
/// <summary>
/// Gets or sets the splashscreen location on disk.
/// </summary>
- /// <remarks>
- /// Not served via the API.
- /// Only used to save the custom uploaded user splashscreen in the configuration file.
- /// </remarks>
- [JsonIgnore]
public string? SplashscreenLocation { get; set; }
}
diff --git a/MediaBrowser.Model/Branding/BrandingOptionsDto.cs b/MediaBrowser.Model/Branding/BrandingOptionsDto.cs
new file mode 100644
index 000000000..c0d8cb31c
--- /dev/null
+++ b/MediaBrowser.Model/Branding/BrandingOptionsDto.cs
@@ -0,0 +1,25 @@
+namespace MediaBrowser.Model.Branding;
+
+/// <summary>
+/// The branding options DTO for API use.
+/// This DTO excludes SplashscreenLocation to prevent it from being updated via API.
+/// </summary>
+public class BrandingOptionsDto
+{
+ /// <summary>
+ /// Gets or sets the login disclaimer.
+ /// </summary>
+ /// <value>The login disclaimer.</value>
+ public string? LoginDisclaimer { get; set; }
+
+ /// <summary>
+ /// Gets or sets the custom CSS.
+ /// </summary>
+ /// <value>The custom CSS.</value>
+ public string? CustomCss { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable the splashscreen.
+ /// </summary>
+ public bool SplashscreenEnabled { get; set; } = false;
+}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index bc4e6ef73..a58c01c96 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;
@@ -178,6 +178,11 @@ public class ServerConfiguration : BaseApplicationConfiguration
public int LibraryUpdateDuration { get; set; } = 30;
/// <summary>
+ /// Gets or sets the maximum amount of items to cache.
+ /// </summary>
+ public int CacheSize { get; set; } = Environment.ProcessorCount * 100;
+
+ /// <summary>
/// Gets or sets the image saving convention.
/// </summary>
/// <value>The image saving convention.</value>
@@ -199,7 +204,9 @@ public class ServerConfiguration : BaseApplicationConfiguration
public bool EnableFolderView { get; set; } = false;
- public bool EnableGroupingIntoCollections { get; set; } = false;
+ public bool EnableGroupingMoviesIntoCollections { get; set; } = false;
+
+ public bool EnableGroupingShowsIntoCollections { get; set; } = false;
public bool DisplaySpecialsWithinSeasons { get; set; } = true;
@@ -249,7 +256,7 @@ public class ServerConfiguration : BaseApplicationConfiguration
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; }
@@ -276,4 +283,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/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index b477f2593..fe4b2de65 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -1,7 +1,7 @@
#pragma warning disable CS1591
using System;
-using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
namespace MediaBrowser.Model.Configuration
{
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index af0787990..1b61bfe15 100644
--- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -25,9 +25,10 @@ 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="numStreams">The number of streams.</param>
/// <param name="numVideoStreams">The number of video streams.</param>
/// <param name="numAudioStreams">The number of audio streams.</param>
/// <param name="videoCodecTag">The video codec tag.</param>
@@ -48,6 +49,7 @@ namespace MediaBrowser.Model.Dlna
bool? isAnamorphic,
bool? isInterlaced,
int? refFrames,
+ int numStreams,
int? numVideoStreams,
int? numAudioStreams,
string? videoCodecTag,
@@ -83,6 +85,8 @@ namespace MediaBrowser.Model.Dlna
return IsConditionSatisfied(condition, width);
case ProfileConditionValue.RefFrames:
return IsConditionSatisfied(condition, refFrames);
+ case ProfileConditionValue.NumStreams:
+ return IsConditionSatisfied(condition, numStreams);
case ProfileConditionValue.NumAudioStreams:
return IsConditionSatisfied(condition, numAudioStreams);
case ProfileConditionValue.NumVideoStreams:
@@ -341,6 +345,15 @@ namespace MediaBrowser.Model.Dlna
return !condition.IsRequired;
}
+ // Special case: HDR10 also satisfies if the video is HDR10Plus
+ if (currentValue.Value == VideoRangeType.HDR10Plus)
+ {
+ if (IsConditionSatisfied(condition, VideoRangeType.HDR10))
+ {
+ return true;
+ }
+ }
+
var conditionType = condition.Condition;
if (conditionType == ProfileConditionType.EqualsAny)
{
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/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
index a32433e18..b66a15840 100644
--- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
+++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
@@ -27,6 +27,7 @@ namespace MediaBrowser.Model.Dlna
IsInterlaced = 21,
AudioSampleRate = 22,
AudioBitDepth = 23,
- VideoRangeType = 24
+ VideoRangeType = 24,
+ NumStreams = 25
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 767e01202..61e04a813 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.
@@ -101,21 +101,16 @@ namespace MediaBrowser.Model.Dlna
MediaStream audioStream = item.GetDefaultAudioStream(null);
+ ArgumentNullException.ThrowIfNull(audioStream);
+
var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options);
var directPlayMethod = directPlayInfo.PlayMethod;
var transcodeReasons = directPlayInfo.TranscodeReasons;
- var inputAudioChannels = audioStream?.Channels;
- var inputAudioBitrate = audioStream?.BitRate;
- var inputAudioSampleRate = audioStream?.SampleRate;
- var inputAudioBitDepth = audioStream?.BitDepth;
-
if (directPlayMethod is PlayMethod.DirectPlay)
{
- var profile = options.Profile;
- var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true);
- var audioFailureReasons = AggregateFailureConditions(item, profile, "AudioCodecProfile", audioFailureConditions);
+ var audioFailureReasons = GetCompatibilityAudioCodec(options, item, item.Container, audioStream, null, false, false);
transcodeReasons |= audioFailureReasons;
if (audioFailureReasons == 0)
@@ -188,6 +183,11 @@ namespace MediaBrowser.Model.Dlna
SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile);
+ var inputAudioChannels = audioStream.Channels;
+ var inputAudioBitrate = audioStream.BitRate;
+ var inputAudioSampleRate = audioStream.SampleRate;
+ var inputAudioBitDepth = audioStream.BitDepth;
+
var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcodingProfile.Container, transcodingProfile.AudioCodec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, false).ToArray();
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true);
@@ -338,6 +338,9 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.IsSecondaryAudio:
return TranscodeReason.SecondaryAudioNotSupported;
+ case ProfileConditionValue.NumStreams:
+ return TranscodeReason.StreamCountExceedsLimit;
+
case ProfileConditionValue.NumAudioStreams:
// TODO
return 0;
@@ -662,15 +665,39 @@ namespace MediaBrowser.Model.Dlna
// Collect candidate audio streams
ICollection<MediaStream> candidateAudioStreams = audioStream is null ? [] : [audioStream];
- if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0)
+ // When the index is explicitly required by client or the default is specified by user, don't do any stream reselection.
+ if (!item.DefaultAudioIndexSource.HasFlag(AudioIndexSource.User) && (options.AudioStreamIndex is null or < 0))
{
- if (audioStream?.IsDefault == true)
+ // When user has no preferences allow stream selection on all streams.
+ if (item.DefaultAudioIndexSource == AudioIndexSource.None && audioStream is not null)
{
- candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.IsDefault).ToArray();
+ candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio).ToArray();
+ if (audioStream.IsDefault)
+ {
+ // If default is picked, only allow selection within default streams.
+ candidateAudioStreams = candidateAudioStreams.Where(stream => stream.IsDefault).ToArray();
+ }
}
- else
+
+ if (item.DefaultAudioIndexSource.HasFlag(AudioIndexSource.Language))
{
+ // If user has language preference, only allow stream selection within the same language.
candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.Language == audioStream?.Language).ToArray();
+ if (item.DefaultAudioIndexSource.HasFlag(AudioIndexSource.Default))
+ {
+ var defaultStreamsInPreferredLanguage = candidateAudioStreams.Where(stream => stream.IsDefault).ToArray();
+
+ // If the user also prefers default streams, try limit selection within default tracks in the same language.
+ // If there is no default stream in the preferred language, allow selection on all default streams to match the "Play default audio track regardless of language" setting.
+ candidateAudioStreams = defaultStreamsInPreferredLanguage.Length > 0
+ ? defaultStreamsInPreferredLanguage
+ : item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.IsDefault).ToArray();
+ }
+ }
+ else if (item.DefaultAudioIndexSource.HasFlag(AudioIndexSource.Default))
+ {
+ // If user prefers default streams, only allow stream selection on default streams.
+ candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.IsDefault).ToArray();
}
}
@@ -797,7 +824,7 @@ namespace MediaBrowser.Model.Dlna
options.SubtitleStreamIndex,
playlistItem.PlayMethod,
playlistItem.TranscodeReasons,
- playlistItem.ToUrl("media:", "<token>"));
+ playlistItem.ToUrl("media:", "<token>", null));
item.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile);
return playlistItem;
@@ -810,6 +837,10 @@ namespace MediaBrowser.Model.Dlna
MediaStream? audioStream,
StreamInfo playlistItem)
{
+ var mediaSource = playlistItem.MediaSource;
+
+ ArgumentNullException.ThrowIfNull(mediaSource);
+
if (!(item.SupportsTranscoding || item.SupportsDirectStream))
{
return (null, null);
@@ -824,17 +855,7 @@ namespace MediaBrowser.Model.Dlna
}
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;
var analyzedProfiles = transcodingProfiles
.Select(transcodingProfile =>
@@ -843,37 +864,40 @@ namespace MediaBrowser.Model.Dlna
var container = transcodingProfile.Container;
- if (options.AllowVideoStreamCopy)
+ if (videoStream is not null
+ && options.AllowVideoStreamCopy
+ && ContainerHelper.ContainsContainer(transcodingProfile.VideoCodec, videoCodec))
{
- 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;
- }
+ var failures = GetCompatibilityVideoCodec(options, mediaSource, container, videoStream);
+ rank.Video = failures == 0 ? 1 : 2;
}
- if (options.AllowAudioStreamCopy)
+ if (audioStream is not null
+ && 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.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 failures = GetCompatibilityAudioCodec(options, mediaSource, container, audioStream, transcodingAudioCodec, true, false);
+
+ var rankAudio = 3;
+
+ if (failures == 0)
+ {
+ rankAudio = string.Equals(transcodingAudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) ? 1 : 2;
+ }
+
+ rank.Audio = Math.Min(rank.Audio, rankAudio);
+
+ if (rank.Audio == 1)
+ {
+ break;
+ }
}
}
@@ -963,9 +987,18 @@ 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 directAudioFailures = audioStreamWithSupportedCodec is null ? default : GetCompatibilityAudioCodec(options, item, container ?? string.Empty, audioStreamWithSupportedCodec, null, true, false);
+
+ playlistItem.TranscodeReasons |= directAudioFailures;
+
+ var directAudioStreamSatisfied = audioStreamWithSupportedCodec is not null && !channelsExceedsLimit
+ && directAudioFailures == 0;
+
+ directAudioStreamSatisfied = directAudioStreamSatisfied && !playlistItem.TranscodeReasons.HasFlag(TranscodeReason.ContainerBitrateExceedsLimit);
- var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && directAudioStream is null;
+ var directAudioStream = directAudioStreamSatisfied ? audioStreamWithSupportedCodec : null;
if (channelsExceedsLimit && playlistItem.TargetAudioStream is not null)
{
@@ -1013,6 +1046,7 @@ namespace MediaBrowser.Model.Dlna
int? packetLength = videoStream?.PacketLength;
int? refFrames = videoStream?.RefFrames;
+ int numStreams = item.MediaStreams.Count;
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
@@ -1021,7 +1055,7 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(playlistItem.VideoCodecs, container, useSubContainer) &&
- i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)))
+ i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numStreams, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)))
// Reverse codec profiles for backward compatibility - first codec profile has higher priority
.Reverse();
foreach (var condition in appliedVideoConditions)
@@ -1087,12 +1121,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)
@@ -1251,52 +1285,14 @@ namespace MediaBrowser.Model.Dlna
DeviceProfile profile = options.Profile;
string container = mediaSource.Container;
- // Video
- int? width = videoStream?.Width;
- int? height = videoStream?.Height;
- int? bitDepth = videoStream?.BitDepth;
- int? videoBitrate = videoStream?.BitRate;
- double? videoLevel = videoStream?.Level;
- string? videoProfile = videoStream?.Profile;
- VideoRangeType? videoRangeType = videoStream?.VideoRangeType;
- float videoFramerate = videoStream is null ? 0 : videoStream.ReferenceFrameRate ?? 0;
- bool? isAnamorphic = videoStream?.IsAnamorphic;
- bool? isInterlaced = videoStream?.IsInterlaced;
- string? videoCodecTag = videoStream?.CodecTag;
- bool? isAvc = videoStream?.IsAVC;
-
- TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
- int? packetLength = videoStream?.PacketLength;
- int? refFrames = videoStream?.RefFrames;
-
- int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
- int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
-
- var checkVideoConditions = (ProfileCondition[] conditions) =>
- conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
-
// Check container conditions
- var containerProfileReasons = AggregateFailureConditions(
- mediaSource,
- profile,
- "VideoCodecProfile",
- profile.ContainerProfiles
- .Where(containerProfile => containerProfile.Type == DlnaProfileType.Video && containerProfile.ContainsContainer(container))
- .SelectMany(containerProfile => checkVideoConditions(containerProfile.Conditions)));
+ var containerProfileReasons = GetCompatibilityContainer(options, mediaSource, container, videoStream);
// Check video conditions
- var videoCodecProfileReasons = AggregateFailureConditions(
- mediaSource,
- profile,
- "VideoCodecProfile",
- profile.CodecProfiles
- .Where(codecProfile => codecProfile.Type == CodecType.Video &&
- codecProfile.ContainsAnyCodec(videoStream?.Codec, container) &&
- !checkVideoConditions(codecProfile.ApplyConditions).Any())
- .SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
+ var videoCodecProfileReasons = videoStream is null ? default : GetCompatibilityVideoCodec(options, mediaSource, container, videoStream);
// Check audio candidates profile conditions
- var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream));
+ var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => GetCompatibilityAudioCodecDirect(options, mediaSource, container, audioStream, true, mediaSource.IsSecondaryAudio(audioStream) ?? false));
TranscodeReason subtitleProfileReasons = 0;
if (subtitleStream is not null)
@@ -1409,20 +1405,6 @@ namespace MediaBrowser.Model.Dlna
return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons);
}
- private TranscodeReason CheckVideoAudioStreamDirectPlay(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream)
- {
- var profile = options.Profile;
- var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream));
-
- var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions);
- if (audioStream.IsExternal == true)
- {
- audioStreamFailureReasons |= TranscodeReason.AudioIsExternal;
- }
-
- return audioStreamFailureReasons;
- }
-
private TranscodeReason AggregateFailureConditions(MediaSourceInfo mediaSource, DeviceProfile profile, string type, IEnumerable<ProfileCondition> conditions)
{
return conditions.Aggregate<ProfileCondition, TranscodeReason>(0, (reasons, i) =>
@@ -1896,6 +1878,7 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.AudioProfile:
case ProfileConditionValue.Has64BitOffsets:
case ProfileConditionValue.PacketLength:
+ case ProfileConditionValue.NumStreams:
case ProfileConditionValue.NumAudioStreams:
case ProfileConditionValue.NumVideoStreams:
case ProfileConditionValue.IsSecondaryAudio:
@@ -2213,7 +2196,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))
@@ -2221,6 +2204,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))
@@ -2235,19 +2232,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)
@@ -2266,5 +2260,141 @@ namespace MediaBrowser.Model.Dlna
return index;
}
+
+ /// <summary>
+ /// Check the profile conditions.
+ /// </summary>
+ /// <param name="conditions">Profile conditions.</param>
+ /// <param name="mediaSource">Media source.</param>
+ /// <param name="videoStream">Video stream.</param>
+ /// <returns>Failed profile conditions.</returns>
+ private IEnumerable<ProfileCondition> CheckVideoConditions(ProfileCondition[] conditions, MediaSourceInfo mediaSource, MediaStream? videoStream)
+ {
+ int? width = videoStream?.Width;
+ int? height = videoStream?.Height;
+ int? bitDepth = videoStream?.BitDepth;
+ int? videoBitrate = videoStream?.BitRate;
+ double? videoLevel = videoStream?.Level;
+ string? videoProfile = videoStream?.Profile;
+ VideoRangeType? videoRangeType = videoStream?.VideoRangeType;
+ float videoFramerate = videoStream is null ? 0 : videoStream.ReferenceFrameRate ?? 0;
+ bool? isAnamorphic = videoStream?.IsAnamorphic;
+ bool? isInterlaced = videoStream?.IsInterlaced;
+ string? videoCodecTag = videoStream?.CodecTag;
+ bool? isAvc = videoStream?.IsAVC;
+
+ TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
+ int? packetLength = videoStream?.PacketLength;
+ int? refFrames = videoStream?.RefFrames;
+
+ int numStreams = mediaSource.MediaStreams.Count;
+ int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
+ int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
+
+ return conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numStreams, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
+ }
+
+ /// <summary>
+ /// Check the compatibility of the container.
+ /// </summary>
+ /// <param name="options">Media options.</param>
+ /// <param name="mediaSource">Media source.</param>
+ /// <param name="container">Container.</param>
+ /// <param name="videoStream">Video stream.</param>
+ /// <returns>Transcode reasons if the container is not fully compatible.</returns>
+ private TranscodeReason GetCompatibilityContainer(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream? videoStream)
+ {
+ var profile = options.Profile;
+
+ var failures = AggregateFailureConditions(
+ mediaSource,
+ profile,
+ "VideoCodecProfile",
+ profile.ContainerProfiles
+ .Where(containerProfile => containerProfile.Type == DlnaProfileType.Video && containerProfile.ContainsContainer(container))
+ .SelectMany(containerProfile => CheckVideoConditions(containerProfile.Conditions, mediaSource, videoStream)));
+
+ return failures;
+ }
+
+ /// <summary>
+ /// Check the compatibility of the video codec.
+ /// </summary>
+ /// <param name="options">Media options.</param>
+ /// <param name="mediaSource">Media source.</param>
+ /// <param name="container">Container.</param>
+ /// <param name="videoStream">Video stream.</param>
+ /// <returns>Transcode reasons if the video stream is not fully compatible.</returns>
+ private TranscodeReason GetCompatibilityVideoCodec(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream videoStream)
+ {
+ var profile = options.Profile;
+
+ string videoCodec = videoStream.Codec;
+
+ var failures = AggregateFailureConditions(
+ mediaSource,
+ profile,
+ "VideoCodecProfile",
+ profile.CodecProfiles
+ .Where(codecProfile => codecProfile.Type == CodecType.Video &&
+ codecProfile.ContainsAnyCodec(videoCodec, container) &&
+ !CheckVideoConditions(codecProfile.ApplyConditions, mediaSource, videoStream).Any())
+ .SelectMany(codecProfile => CheckVideoConditions(codecProfile.Conditions, mediaSource, videoStream)));
+
+ return failures;
+ }
+
+ /// <summary>
+ /// Check the compatibility of the audio codec.
+ /// </summary>
+ /// <param name="options">Media options.</param>
+ /// <param name="mediaSource">Media source.</param>
+ /// <param name="container">Container.</param>
+ /// <param name="audioStream">Audio stream.</param>
+ /// <param name="transcodingAudioCodec">Override audio codec.</param>
+ /// <param name="isVideo">The media source is video.</param>
+ /// <param name="isSecondaryAudio">The audio stream is secondary.</param>
+ /// <returns>Transcode reasons if the audio stream is not fully compatible.</returns>
+ private TranscodeReason GetCompatibilityAudioCodec(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream, string? transcodingAudioCodec, bool isVideo, bool isSecondaryAudio)
+ {
+ var profile = options.Profile;
+
+ var audioCodec = transcodingAudioCodec ?? audioStream.Codec;
+ var audioProfile = audioStream.Profile;
+ var audioChannels = audioStream.Channels;
+ var audioBitrate = audioStream.BitRate;
+ var audioSampleRate = audioStream.SampleRate;
+ var audioBitDepth = audioStream.BitDepth;
+
+ var audioFailureConditions = isVideo
+ ? GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioCodec, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio)
+ : GetProfileConditionsForAudio(profile.CodecProfiles, container, audioCodec, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, true);
+
+ var failures = AggregateFailureConditions(mediaSource, profile, "AudioCodecProfile", audioFailureConditions);
+
+ return failures;
+ }
+
+ /// <summary>
+ /// Check the compatibility of the audio codec for direct playback.
+ /// </summary>
+ /// <param name="options">Media options.</param>
+ /// <param name="mediaSource">Media source.</param>
+ /// <param name="container">Container.</param>
+ /// <param name="audioStream">Audio stream.</param>
+ /// <param name="isVideo">The media source is video.</param>
+ /// <param name="isSecondaryAudio">The audio stream is secondary.</param>
+ /// <returns>Transcode reasons if the audio stream is not fully compatible for direct playback.</returns>
+ private TranscodeReason GetCompatibilityAudioCodecDirect(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream, bool isVideo, bool isSecondaryAudio)
+ {
+ var failures = GetCompatibilityAudioCodec(options, mediaSource, container, audioStream, null, isVideo, isSecondaryAudio);
+
+ if (audioStream.IsExternal)
+ {
+ failures |= TranscodeReason.AudioIsExternal;
+ }
+
+ return failures;
+ }
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 1ae4e1962..13acd15a3 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -1,7 +1,13 @@
+#pragma warning disable CA1819 // Properties should not return arrays
+
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
+using System.Linq;
+using System.Text;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -871,202 +877,271 @@ public class StreamInfo
/// </summary>
/// <param name="baseUrl">The base Url.</param>
/// <param name="accessToken">The access Token.</param>
+ /// <param name="query">Optional extra query.</param>
/// <returns>A querystring representation of this object.</returns>
- public string ToUrl(string baseUrl, string? accessToken)
+ public string ToUrl(string? baseUrl, string? accessToken, string? query)
{
- ArgumentException.ThrowIfNullOrEmpty(baseUrl);
+ var sb = new StringBuilder();
+ if (!string.IsNullOrEmpty(baseUrl))
+ {
+ sb.Append(baseUrl.TrimEnd('/'));
+ }
- List<string> list = [];
- foreach (NameValuePair pair in BuildParams(this, accessToken))
+ if (MediaType == DlnaProfileType.Audio)
{
- if (string.IsNullOrEmpty(pair.Value))
- {
- continue;
- }
+ sb.Append("/audio/");
+ }
+ else
+ {
+ sb.Append("/videos/");
+ }
- // 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;
- }
+ sb.Append(ItemId);
- if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase)
- && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
+ if (SubProtocol == MediaStreamProtocol.hls)
+ {
+ sb.Append("/master.m3u8?");
+ }
+ else
+ {
+ sb.Append("/stream");
- if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase)
- && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(Container))
{
- continue;
+ sb.Append('.');
+ sb.Append(Container);
}
- var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal);
+ sb.Append('?');
+ }
- list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
+ if (!string.IsNullOrEmpty(DeviceProfileId))
+ {
+ sb.Append("&DeviceProfileId=");
+ sb.Append(DeviceProfileId);
}
- string queryString = string.Join('&', list);
+ if (!string.IsNullOrEmpty(DeviceId))
+ {
+ sb.Append("&DeviceId=");
+ sb.Append(DeviceId);
+ }
- return GetUrl(baseUrl, queryString);
- }
+ if (!string.IsNullOrEmpty(MediaSourceId))
+ {
+ sb.Append("&MediaSourceId=");
+ sb.Append(MediaSourceId);
+ }
- private string GetUrl(string baseUrl, string queryString)
- {
- ArgumentException.ThrowIfNullOrEmpty(baseUrl);
+ // default true so don't store.
+ if (IsDirectStream)
+ {
+ sb.Append("&Static=true");
+ }
- string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
+ if (VideoCodecs.Count != 0)
+ {
+ sb.Append("&VideoCodec=");
+ sb.AppendJoin(',', VideoCodecs);
+ }
- baseUrl = baseUrl.TrimEnd('/');
+ if (AudioCodecs.Count != 0)
+ {
+ sb.Append("&AudioCodec=");
+ sb.AppendJoin(',', AudioCodecs);
+ }
- if (MediaType == DlnaProfileType.Audio)
+ if (AudioStreamIndex.HasValue)
{
- if (SubProtocol == MediaStreamProtocol.hls)
- {
- return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
- }
+ sb.Append("&AudioStreamIndex=");
+ sb.Append(AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture));
+ }
- return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
+ if (SubtitleStreamIndex.HasValue && (AlwaysBurnInSubtitleWhenTranscoding || SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) && SubtitleStreamIndex != -1)
+ {
+ sb.Append("&SubtitleStreamIndex=");
+ sb.Append(SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture));
}
- if (SubProtocol == MediaStreamProtocol.hls)
+ if (VideoBitrate.HasValue)
{
- return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
+ sb.Append("&VideoBitrate=");
+ sb.Append(VideoBitrate.Value.ToString(CultureInfo.InvariantCulture));
}
- return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
- }
+ if (AudioBitrate.HasValue)
+ {
+ sb.Append("&AudioBitrate=");
+ sb.Append(AudioBitrate.Value.ToString(CultureInfo.InvariantCulture));
+ }
- private static List<NameValuePair> BuildParams(StreamInfo item, string? accessToken)
- {
- List<NameValuePair> list = [];
+ if (AudioSampleRate.HasValue)
+ {
+ sb.Append("&AudioSampleRate=");
+ sb.Append(AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
+ }
- string audioCodecs = item.AudioCodecs.Count == 0 ?
- string.Empty :
- string.Join(',', item.AudioCodecs);
+ if (MaxFramerate.HasValue)
+ {
+ sb.Append("&MaxFramerate=");
+ sb.Append(MaxFramerate.Value.ToString(CultureInfo.InvariantCulture));
+ }
- string videoCodecs = item.VideoCodecs.Count == 0 ?
- string.Empty :
- string.Join(',', item.VideoCodecs);
+ if (MaxWidth.HasValue)
+ {
+ sb.Append("&MaxWidth=");
+ sb.Append(MaxWidth.Value.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.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));
+ if (MaxHeight.HasValue)
+ {
+ sb.Append("&MaxHeight=");
+ sb.Append(MaxHeight.Value.ToString(CultureInfo.InvariantCulture));
+ }
+
+ if (SubProtocol == MediaStreamProtocol.hls)
+ {
+ if (!string.IsNullOrEmpty(Container))
+ {
+ sb.Append("&SegmentContainer=");
+ sb.Append(Container);
+ }
- 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));
+ if (SegmentLength.HasValue)
+ {
+ sb.Append("&SegmentLength=");
+ sb.Append(SegmentLength.Value.ToString(CultureInfo.InvariantCulture));
+ }
- long startPositionTicks = item.StartPositionTicks;
+ if (MinSegments.HasValue)
+ {
+ sb.Append("&MinSegments=");
+ sb.Append(MinSegments.Value.ToString(CultureInfo.InvariantCulture));
+ }
- if (item.SubProtocol == MediaStreamProtocol.hls)
- {
- list.Add(new NameValuePair("StartTimeTicks", string.Empty));
+ sb.Append("&BreakOnNonKeyFrames=");
+ sb.Append(BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture));
}
else
{
- list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
+ if (StartPositionTicks != 0)
+ {
+ sb.Append("&StartTimeTicks=");
+ sb.Append(StartPositionTicks.ToString(CultureInfo.InvariantCulture));
+ }
}
- list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
- list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
+ if (!string.IsNullOrEmpty(PlaySessionId))
+ {
+ sb.Append("&PlaySessionId=");
+ sb.Append(PlaySessionId);
+ }
- string? liveStreamId = item.MediaSource?.LiveStreamId;
- list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
+ if (!string.IsNullOrEmpty(accessToken))
+ {
+ sb.Append("&ApiKey=");
+ sb.Append(accessToken);
+ }
- list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
+ var liveStreamId = MediaSource?.LiveStreamId;
+ if (!string.IsNullOrEmpty(liveStreamId))
+ {
+ sb.Append("&LiveStreamId=");
+ sb.Append(liveStreamId);
+ }
- if (!item.IsDirectStream)
+ if (!IsDirectStream)
{
- if (item.RequireNonAnamorphic)
+ if (RequireNonAnamorphic)
{
- list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ sb.Append("&RequireNonAnamorphic=");
+ sb.Append(RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture));
}
- list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
+ if (TranscodingMaxAudioChannels.HasValue)
+ {
+ sb.Append("&TranscodingMaxAudioChannels=");
+ sb.Append(TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
+ }
- if (item.EnableSubtitlesInManifest)
+ if (EnableSubtitlesInManifest)
{
- list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ sb.Append("&EnableSubtitlesInManifest=");
+ sb.Append(EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture));
}
- if (item.EnableMpegtsM2TsMode)
+ if (EnableMpegtsM2TsMode)
{
- list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ sb.Append("&EnableMpegtsM2TsMode=");
+ sb.Append(EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture));
}
- if (item.EstimateContentLength)
+ if (EstimateContentLength)
{
- list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ sb.Append("&EstimateContentLength=");
+ sb.Append(EstimateContentLength.ToString(CultureInfo.InvariantCulture));
}
- if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
+ if (TranscodeSeekInfo != TranscodeSeekInfo.Auto)
{
- list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
+ sb.Append("&TranscodeSeekInfo=");
+ sb.Append(TranscodeSeekInfo.ToString());
}
- if (item.CopyTimestamps)
+ if (CopyTimestamps)
{
- list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ sb.Append("&CopyTimestamps=");
+ sb.Append(CopyTimestamps.ToString(CultureInfo.InvariantCulture));
}
- list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ sb.Append("&RequireAvc=");
+ sb.Append(RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
- list.Add(new NameValuePair("EnableAudioVbrEncoding", item.EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
+ sb.Append("&EnableAudioVbrEncoding=");
+ sb.Append(EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
}
- list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty));
-
- string subtitleCodecs = item.SubtitleCodecs.Count == 0 ?
- string.Empty :
- string.Join(",", item.SubtitleCodecs);
-
- list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
-
- if (item.SubProtocol == MediaStreamProtocol.hls)
+ var etag = MediaSource?.ETag;
+ if (!string.IsNullOrEmpty(etag))
{
- list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
-
- if (item.SegmentLength.HasValue)
- {
- list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
- }
+ sb.Append("&Tag=");
+ sb.Append(etag);
+ }
- if (item.MinSegments.HasValue)
- {
- list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
- }
+ if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod != SubtitleDeliveryMethod.External)
+ {
+ sb.Append("&SubtitleMethod=");
+ sb.Append(SubtitleDeliveryMethod);
+ }
- list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
+ if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed && SubtitleCodecs.Count != 0)
+ {
+ sb.Append("&SubtitleCodec=");
+ sb.AppendJoin(',', SubtitleCodecs);
}
- foreach (var pair in item.StreamOptions)
+ foreach (var pair in StreamOptions)
{
- if (string.IsNullOrEmpty(pair.Value))
- {
- continue;
- }
+ // Strip spaces to avoid having to encode h264 profile names
+ sb.Append('&');
+ sb.Append(pair.Key);
+ sb.Append('=');
+ sb.Append(pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal));
+ }
- // strip spaces to avoid having to encode h264 profile names
- list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)));
+ var transcodeReasonsValues = TranscodeReasons.GetUniqueFlags().ToArray();
+ if (!IsDirectStream && transcodeReasonsValues.Length > 0)
+ {
+ sb.Append("&TranscodeReasons=");
+ sb.AppendJoin(',', transcodeReasonsValues);
}
- if (!item.IsDirectStream)
+ if (!string.IsNullOrEmpty(query))
{
- list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString()));
+ sb.Append(query);
}
- return list;
+ return sb.ToString();
}
/// <summary>
@@ -1189,7 +1264,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/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 7e8949e1f..937409111 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -3,8 +3,9 @@
using System;
using System.Collections.Generic;
-using Jellyfin.Data.Entities;
+using System.ComponentModel;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Library;
@@ -550,7 +551,7 @@ namespace MediaBrowser.Model.Dto
/// Gets or sets the parent primary image item identifier.
/// </summary>
/// <value>The parent primary image item identifier.</value>
- public string ParentPrimaryImageItemId { get; set; }
+ public Guid? ParentPrimaryImageItemId { get; set; }
/// <summary>
/// Gets or sets the parent primary image tag.
@@ -586,6 +587,7 @@ namespace MediaBrowser.Model.Dto
/// Gets or sets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
+ [DefaultValue(MediaType.Unknown)]
public MediaType MediaType { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs
index d3bcf492d..80e2cfb08 100644
--- a/MediaBrowser.Model/Dto/BaseItemPerson.cs
+++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs
@@ -1,6 +1,7 @@
#nullable disable
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities;
@@ -34,6 +35,7 @@ namespace MediaBrowser.Model.Dto
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
+ [DefaultValue(PersonKind.Unknown)]
public PersonKind Type { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs
index 5963ed270..d481593cd 100644
--- a/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs
+++ b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs
@@ -15,13 +15,13 @@ public class ClientCapabilitiesDto
/// <summary>
/// Gets or sets the list of playable media types.
/// </summary>
- [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
+ [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = [];
/// <summary>
/// Gets or sets the list of supported commands.
/// </summary>
- [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
+ [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = [];
/// <summary>
diff --git a/MediaBrowser.Model/Dto/DisplayPreferencesDto.cs b/MediaBrowser.Model/Dto/DisplayPreferencesDto.cs
index 90163ae91..54cbe65f6 100644
--- a/MediaBrowser.Model/Dto/DisplayPreferencesDto.cs
+++ b/MediaBrowser.Model/Dto/DisplayPreferencesDto.cs
@@ -1,5 +1,5 @@
using System.Collections.Generic;
-using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
namespace MediaBrowser.Model.Dto
{
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index eff2e09da..75ccdcf27 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -1,12 +1,10 @@
#nullable disable
#pragma warning disable CS1591
-using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
-using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
@@ -17,15 +15,16 @@ namespace MediaBrowser.Model.Dto
{
public MediaSourceInfo()
{
- Formats = Array.Empty<string>();
- MediaStreams = Array.Empty<MediaStream>();
- MediaAttachments = Array.Empty<MediaAttachment>();
- RequiredHttpHeaders = new Dictionary<string, string>();
+ Formats = [];
+ MediaStreams = [];
+ MediaAttachments = [];
+ RequiredHttpHeaders = [];
SupportsTranscoding = true;
SupportsDirectStream = true;
SupportsDirectPlay = true;
SupportsProbing = true;
UseMostCompatibleTranscodingProfile = false;
+ DefaultAudioIndexSource = AudioIndexSource.None;
}
public MediaProtocol Protocol { get; set; }
@@ -120,6 +119,9 @@ namespace MediaBrowser.Model.Dto
[JsonIgnore]
public TranscodeReason TranscodeReasons { get; set; }
+ [JsonIgnore]
+ public AudioIndexSource DefaultAudioIndexSource { get; set; }
+
public int? DefaultAudioStreamIndex { get; set; }
public int? DefaultSubtitleStreamIndex { get; set; }
diff --git a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
index a3035bf61..2f3a5d117 100644
--- a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
+++ b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
@@ -1,35 +1,55 @@
-#pragma warning disable CS1591
-
-using System;
using System.Collections.Generic;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Providers;
-namespace MediaBrowser.Model.Dto
+namespace MediaBrowser.Model.Dto;
+
+/// <summary>
+/// A class representing metadata editor information.
+/// </summary>
+public class MetadataEditorInfo
{
- public class MetadataEditorInfo
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MetadataEditorInfo"/> class.
+ /// </summary>
+ public MetadataEditorInfo()
{
- public MetadataEditorInfo()
- {
- ParentalRatingOptions = Array.Empty<ParentalRating>();
- Countries = Array.Empty<CountryInfo>();
- Cultures = Array.Empty<CultureDto>();
- ExternalIdInfos = Array.Empty<ExternalIdInfo>();
- ContentTypeOptions = Array.Empty<NameValuePair>();
- }
-
- public IReadOnlyList<ParentalRating> ParentalRatingOptions { get; set; }
-
- public IReadOnlyList<CountryInfo> Countries { get; set; }
-
- public IReadOnlyList<CultureDto> Cultures { get; set; }
-
- public IReadOnlyList<ExternalIdInfo> ExternalIdInfos { get; set; }
-
- public CollectionType? ContentType { get; set; }
-
- public IReadOnlyList<NameValuePair> ContentTypeOptions { get; set; }
+ ParentalRatingOptions = [];
+ Countries = [];
+ Cultures = [];
+ ExternalIdInfos = [];
+ ContentTypeOptions = [];
}
+
+ /// <summary>
+ /// Gets or sets the parental rating options.
+ /// </summary>
+ public IReadOnlyList<ParentalRating> ParentalRatingOptions { get; set; }
+
+ /// <summary>
+ /// Gets or sets the countries.
+ /// </summary>
+ public IReadOnlyList<CountryInfo> Countries { get; set; }
+
+ /// <summary>
+ /// Gets or sets the cultures.
+ /// </summary>
+ public IReadOnlyList<CultureDto> Cultures { get; set; }
+
+ /// <summary>
+ /// Gets or sets the external id infos.
+ /// </summary>
+ public IReadOnlyList<ExternalIdInfo> ExternalIdInfos { get; set; }
+
+ /// <summary>
+ /// Gets or sets the content type.
+ /// </summary>
+ public CollectionType? ContentType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the content type options.
+ /// </summary>
+ public IReadOnlyList<NameValuePair> ContentTypeOptions { get; set; }
}
diff --git a/MediaBrowser.Model/Dto/SessionInfoDto.cs b/MediaBrowser.Model/Dto/SessionInfoDto.cs
index 2496c933a..d727cd874 100644
--- a/MediaBrowser.Model/Dto/SessionInfoDto.cs
+++ b/MediaBrowser.Model/Dto/SessionInfoDto.cs
@@ -163,7 +163,7 @@ public class SessionInfoDto
/// <summary>
/// Gets or sets the playlist item id.
/// </summary>
- /// <value>The splaylist item id.</value>
+ /// <value>The playlist item id.</value>
public string? PlaylistItemId { get; set; }
/// <summary>
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/MediaAttachment.cs b/MediaBrowser.Model/Entities/MediaAttachment.cs
index 34e3eabc9..f8f7ad0f9 100644
--- a/MediaBrowser.Model/Entities/MediaAttachment.cs
+++ b/MediaBrowser.Model/Entities/MediaAttachment.cs
@@ -1,51 +1,49 @@
-#nullable disable
-namespace MediaBrowser.Model.Entities
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// Class MediaAttachment.
+/// </summary>
+public class MediaAttachment
{
/// <summary>
- /// Class MediaAttachment.
+ /// Gets or sets the codec.
/// </summary>
- public class MediaAttachment
- {
- /// <summary>
- /// Gets or sets the codec.
- /// </summary>
- /// <value>The codec.</value>
- public string Codec { get; set; }
+ /// <value>The codec.</value>
+ public string? Codec { get; set; }
- /// <summary>
- /// Gets or sets the codec tag.
- /// </summary>
- /// <value>The codec tag.</value>
- public string CodecTag { get; set; }
+ /// <summary>
+ /// Gets or sets the codec tag.
+ /// </summary>
+ /// <value>The codec tag.</value>
+ public string? CodecTag { get; set; }
- /// <summary>
- /// Gets or sets the comment.
- /// </summary>
- /// <value>The comment.</value>
- public string Comment { get; set; }
+ /// <summary>
+ /// Gets or sets the comment.
+ /// </summary>
+ /// <value>The comment.</value>
+ public string? Comment { get; set; }
- /// <summary>
- /// Gets or sets the index.
- /// </summary>
- /// <value>The index.</value>
- public int Index { get; set; }
+ /// <summary>
+ /// Gets or sets the index.
+ /// </summary>
+ /// <value>The index.</value>
+ public int Index { get; set; }
- /// <summary>
- /// Gets or sets the filename.
- /// </summary>
- /// <value>The filename.</value>
- public string FileName { get; set; }
+ /// <summary>
+ /// Gets or sets the filename.
+ /// </summary>
+ /// <value>The filename.</value>
+ public string? FileName { get; set; }
- /// <summary>
- /// Gets or sets the MIME type.
- /// </summary>
- /// <value>The MIME type.</value>
- public string MimeType { get; set; }
+ /// <summary>
+ /// Gets or sets the MIME type.
+ /// </summary>
+ /// <value>The MIME type.</value>
+ public string? MimeType { get; set; }
- /// <summary>
- /// Gets or sets the delivery URL.
- /// </summary>
- /// <value>The delivery URL.</value>
- public string DeliveryUrl { get; set; }
- }
+ /// <summary>
+ /// Gets or sets the delivery URL.
+ /// </summary>
+ /// <value>The delivery URL.</value>
+ public string? DeliveryUrl { get; set; }
}
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 85c1f797b..5c8f37fcd 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -2,6 +2,7 @@
#pragma warning disable CS1591
using System;
+using System.Collections.Frozen;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
@@ -153,10 +154,13 @@ namespace MediaBrowser.Model.Entities
/// <value>The title.</value>
public string Title { get; set; }
+ public bool? Hdr10PlusPresentFlag { get; set; }
+
/// <summary>
/// Gets the video range.
/// </summary>
/// <value>The video range.</value>
+ [DefaultValue(VideoRange.Unknown)]
public VideoRange VideoRange
{
get
@@ -171,6 +175,7 @@ namespace MediaBrowser.Model.Entities
/// Gets the video range type.
/// </summary>
/// <value>The video range type.</value>
+ [DefaultValue(VideoRangeType.Unknown)]
public VideoRangeType VideoRangeType
{
get
@@ -268,7 +273,7 @@ namespace MediaBrowser.Model.Entities
// Do not display the language code in display titles if unset or set to a special code. Show it in all other cases (possibly expanded).
if (!string.IsNullOrEmpty(Language) && !_specialCodes.Contains(Language, StringComparison.OrdinalIgnoreCase))
{
- // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes.
+ // Get full language string i.e. eng -> English.
string fullLanguage = CultureInfo
.GetCultures(CultureTypes.NeutralCultures)
.FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase))
@@ -371,7 +376,7 @@ namespace MediaBrowser.Model.Entities
if (!string.IsNullOrEmpty(Language))
{
- // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes.
+ // Get full language string i.e. eng -> English.
string fullLanguage = CultureInfo
.GetCultures(CultureTypes.NeutralCultures)
.FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase))
@@ -383,7 +388,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);
}
@@ -537,7 +542,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;
}
@@ -778,8 +783,8 @@ namespace MediaBrowser.Model.Entities
var blPresentFlag = BlPresentFlag == 1;
var dvBlCompatId = DvBlSignalCompatibilityId;
- var isDoViProfile = dvProfile == 5 || dvProfile == 7 || dvProfile == 8 || dvProfile == 10;
- var isDoViFlag = rpuPresentFlag && blPresentFlag && (dvBlCompatId == 0 || dvBlCompatId == 1 || dvBlCompatId == 4 || dvBlCompatId == 2 || dvBlCompatId == 6);
+ var isDoViProfile = dvProfile is 5 or 7 or 8 or 10;
+ var isDoViFlag = rpuPresentFlag && blPresentFlag && dvBlCompatId is 0 or 1 or 4 or 2 or 6;
if ((isDoViProfile && isDoViFlag)
|| string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
@@ -787,7 +792,7 @@ namespace MediaBrowser.Model.Entities
|| string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))
{
- return dvProfile switch
+ var dvRangeSet = dvProfile switch
{
5 => (VideoRange.HDR, VideoRangeType.DOVI),
8 => dvBlCompatId switch
@@ -795,32 +800,40 @@ namespace MediaBrowser.Model.Entities
1 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10),
4 => (VideoRange.HDR, VideoRangeType.DOVIWithHLG),
2 => (VideoRange.SDR, VideoRangeType.DOVIWithSDR),
- // 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)
+ // Out of Dolby Spec files should be marked as invalid
+ _ => (VideoRange.HDR, VideoRangeType.DOVIInvalid)
},
- 7 => (VideoRange.HDR, VideoRangeType.HDR10),
+ 7 => (VideoRange.HDR, VideoRangeType.DOVIWithEL),
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)
+ // Out of Dolby Spec files should be marked as invalid
+ _ => (VideoRange.HDR, VideoRangeType.DOVIInvalid)
},
_ => (VideoRange.SDR, VideoRangeType.SDR)
};
+
+ if (Hdr10PlusPresentFlag == true)
+ {
+ return dvRangeSet.Item2 switch
+ {
+ VideoRangeType.DOVIWithHDR10 => (VideoRange.HDR, VideoRangeType.DOVIWithHDR10Plus),
+ VideoRangeType.DOVIWithEL => (VideoRange.HDR, VideoRangeType.DOVIWithELHDR10Plus),
+ _ => dvRangeSet
+ };
+ }
+
+ return dvRangeSet;
}
var colorTransfer = ColorTransfer;
if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase))
{
- return (VideoRange.HDR, VideoRangeType.HDR10);
+ return Hdr10PlusPresentFlag == true ? (VideoRange.HDR, VideoRangeType.HDR10Plus) : (VideoRange.HDR, VideoRangeType.HDR10);
}
else if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs
index bd8db9941..65337b60f 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,
@@ -84,6 +84,11 @@ namespace MediaBrowser.Model.Entities
/// <summary>
/// The TvMaze provider.
/// </summary>
- TvMaze = 19
+ TvMaze = 19,
+
+ /// <summary>
+ /// The MusicBrainz recording provider.
+ /// </summary>
+ MusicBrainzRecording = 20,
}
}
diff --git a/MediaBrowser.Model/Entities/ParentalRating.cs b/MediaBrowser.Model/Entities/ParentalRating.cs
index c92640818..4f1198902 100644
--- a/MediaBrowser.Model/Entities/ParentalRating.cs
+++ b/MediaBrowser.Model/Entities/ParentalRating.cs
@@ -1,33 +1,40 @@
-#nullable disable
-#pragma warning disable CS1591
+namespace MediaBrowser.Model.Entities;
-namespace MediaBrowser.Model.Entities
+/// <summary>
+/// Class ParentalRating.
+/// </summary>
+public class ParentalRating
{
/// <summary>
- /// Class ParentalRating.
+ /// Initializes a new instance of the <see cref="ParentalRating"/> class.
/// </summary>
- public class ParentalRating
+ /// <param name="name">The name.</param>
+ /// <param name="score">The score.</param>
+ public ParentalRating(string name, ParentalRatingScore? score)
{
- public ParentalRating()
- {
- }
+ Name = name;
+ Value = score?.Score;
+ RatingScore = score;
+ }
- public ParentalRating(string name, int? value)
- {
- Name = name;
- Value = value;
- }
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name { get; set; }
+ /// <summary>
+ /// Gets or sets the value.
+ /// </summary>
+ /// <value>The value.</value>
+ /// <remarks>
+ /// Deprecated.
+ /// </remarks>
+ public int? Value { get; set; }
- /// <summary>
- /// Gets or sets the value.
- /// </summary>
- /// <value>The value.</value>
- public int? Value { get; set; }
- }
+ /// <summary>
+ /// Gets or sets the rating score.
+ /// </summary>
+ /// <value>The rating score.</value>
+ public ParentalRatingScore? RatingScore { get; set; }
}
diff --git a/MediaBrowser.Model/Entities/ParentalRatingEntry.cs b/MediaBrowser.Model/Entities/ParentalRatingEntry.cs
new file mode 100644
index 000000000..69be74ac0
--- /dev/null
+++ b/MediaBrowser.Model/Entities/ParentalRatingEntry.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// A class representing an parental rating entry.
+/// </summary>
+public class ParentalRatingEntry
+{
+ /// <summary>
+ /// Gets or sets the rating strings.
+ /// </summary>
+ [JsonPropertyName("ratingStrings")]
+ public required IReadOnlyList<string> RatingStrings { get; set; }
+
+ /// <summary>
+ /// Gets or sets the score.
+ /// </summary>
+ [JsonPropertyName("ratingScore")]
+ public required ParentalRatingScore RatingScore { get; set; }
+}
diff --git a/MediaBrowser.Model/Entities/ParentalRatingScore.cs b/MediaBrowser.Model/Entities/ParentalRatingScore.cs
new file mode 100644
index 000000000..b9bb99685
--- /dev/null
+++ b/MediaBrowser.Model/Entities/ParentalRatingScore.cs
@@ -0,0 +1,32 @@
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// A class representing an parental rating score.
+/// </summary>
+public class ParentalRatingScore
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ParentalRatingScore"/> class.
+ /// </summary>
+ /// <param name="score">The score.</param>
+ /// <param name="subScore">The sub score.</param>
+ public ParentalRatingScore(int score, int? subScore)
+ {
+ Score = score;
+ SubScore = subScore;
+ }
+
+ /// <summary>
+ /// Gets or sets the score.
+ /// </summary>
+ [JsonPropertyName("score")]
+ public int Score { get; set; }
+
+ /// <summary>
+ /// Gets or sets the sub score.
+ /// </summary>
+ [JsonPropertyName("subScore")]
+ public int? SubScore { get; set; }
+}
diff --git a/MediaBrowser.Model/Entities/ParentalRatingSystem.cs b/MediaBrowser.Model/Entities/ParentalRatingSystem.cs
new file mode 100644
index 000000000..b452f2901
--- /dev/null
+++ b/MediaBrowser.Model/Entities/ParentalRatingSystem.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Model.Entities;
+
+/// <summary>
+/// A class representing a parental rating system.
+/// </summary>
+public class ParentalRatingSystem
+{
+ /// <summary>
+ /// Gets or sets the country code.
+ /// </summary>
+ [JsonPropertyName("countryCode")]
+ public required string CountryCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether sub scores are supported.
+ /// </summary>
+ [JsonPropertyName("supportsSubScores")]
+ public bool SupportsSubScores { get; set; }
+
+ /// <summary>
+ /// Gets or sets the ratings.
+ /// </summary>
+ [JsonPropertyName("ratings")]
+ public IReadOnlyList<ParentalRatingEntry>? Ratings { get; set; }
+}
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..848cc2f62 100644
--- a/MediaBrowser.Model/Extensions/ContainerHelper.cs
+++ b/MediaBrowser.Model/Extensions/ContainerHelper.cs
@@ -14,13 +14,14 @@ 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)
{
var isNegativeList = false;
- if (profileContainers != null && profileContainers.StartsWith('-'))
+ if (profileContainers is not null && profileContainers.StartsWith('-'))
{
isNegativeList = true;
profileContainers = profileContainers[1..];
@@ -34,13 +35,14 @@ 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)
{
var isNegativeList = false;
- if (profileContainers != null && profileContainers.StartsWith('-'))
+ if (profileContainers is not null && profileContainers.StartsWith('-'))
{
isNegativeList = true;
profileContainers = profileContainers[1..];
@@ -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/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
index 02a29e7fa..f6e65028e 100644
--- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs
+++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
@@ -1,65 +1,73 @@
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using MediaBrowser.Model.Entities;
-namespace MediaBrowser.Model.Globalization
+namespace MediaBrowser.Model.Globalization;
+
+/// <summary>
+/// Interface ILocalizationManager.
+/// </summary>
+public interface ILocalizationManager
{
/// <summary>
- /// Interface ILocalizationManager.
+ /// Gets the cultures.
/// </summary>
- public interface ILocalizationManager
- {
- /// <summary>
- /// Gets the cultures.
- /// </summary>
- /// <returns><see cref="IEnumerable{CultureDto}" />.</returns>
- IEnumerable<CultureDto> GetCultures();
+ /// <returns><see cref="IEnumerable{CultureDto}" />.</returns>
+ IEnumerable<CultureDto> GetCultures();
- /// <summary>
- /// Gets the countries.
- /// </summary>
- /// <returns><see cref="IEnumerable{CountryInfo}" />.</returns>
- IEnumerable<CountryInfo> GetCountries();
+ /// <summary>
+ /// Gets the countries.
+ /// </summary>
+ /// <returns><see cref="IReadOnlyList{CountryInfo}" />.</returns>
+ IReadOnlyList<CountryInfo> GetCountries();
- /// <summary>
- /// Gets the parental ratings.
- /// </summary>
- /// <returns><see cref="IEnumerable{ParentalRating}" />.</returns>
- IEnumerable<ParentalRating> GetParentalRatings();
+ /// <summary>
+ /// Gets the parental ratings.
+ /// </summary>
+ /// <returns><see cref="IReadOnlyList{ParentalRating}" />.</returns>
+ IReadOnlyList<ParentalRating> GetParentalRatings();
- /// <summary>
- /// Gets the rating level.
- /// </summary>
- /// <param name="rating">The rating.</param>
- /// <param name="countryCode">The optional two letter ISO language string.</param>
- /// <returns><see cref="int" /> or <c>null</c>.</returns>
- int? GetRatingLevel(string rating, string? countryCode = null);
+ /// <summary>
+ /// Gets the rating level.
+ /// </summary>
+ /// <param name="rating">The rating.</param>
+ /// <param name="countryCode">The optional two letter ISO language string.</param>
+ /// <returns><see cref="ParentalRatingScore" /> or <c>null</c>.</returns>
+ ParentalRatingScore? GetRatingScore(string rating, string? countryCode = null);
+
+ /// <summary>
+ /// Gets the localized string.
+ /// </summary>
+ /// <param name="phrase">The phrase.</param>
+ /// <param name="culture">The culture.</param>
+ /// <returns><see cref="string" />.</returns>
+ string GetLocalizedString(string phrase, string culture);
- /// <summary>
- /// Gets the localized string.
- /// </summary>
- /// <param name="phrase">The phrase.</param>
- /// <param name="culture">The culture.</param>
- /// <returns><see cref="string" />.</returns>
- string GetLocalizedString(string phrase, string culture);
+ /// <summary>
+ /// Gets the localized string.
+ /// </summary>
+ /// <param name="phrase">The phrase.</param>
+ /// <returns>System.String.</returns>
+ string GetLocalizedString(string phrase);
- /// <summary>
- /// Gets the localized string.
- /// </summary>
- /// <param name="phrase">The phrase.</param>
- /// <returns>System.String.</returns>
- string GetLocalizedString(string phrase);
+ /// <summary>
+ /// Gets the localization options.
+ /// </summary>
+ /// <returns><see cref="IEnumerable{LocalizationOption}" />.</returns>
+ IEnumerable<LocalizationOption> GetLocalizationOptions();
- /// <summary>
- /// Gets the localization options.
- /// </summary>
- /// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns>
- IEnumerable<LocalizationOption> GetLocalizationOptions();
+ /// <summary>
+ /// Returns the correct <see cref="CultureDto" /> for the given language.
+ /// </summary>
+ /// <param name="language">The language.</param>
+ /// <returns>The correct <see cref="CultureDto" /> for the given language.</returns>
+ CultureDto? FindLanguageInfo(string language);
- /// <summary>
- /// Returns the correct <see cref="CultureDto" /> for the given language.
- /// </summary>
- /// <param name="language">The language.</param>
- /// <returns>The correct <see cref="CultureDto" /> for the given language.</returns>
- CultureDto? FindLanguageInfo(string language);
- }
+ /// <summary>
+ /// Returns the language in ISO 639-2/T when the input is ISO 639-2/B.
+ /// </summary>
+ /// <param name="isoB">The language in ISO 639-2/B.</param>
+ /// <param name="isoT">The language in ISO 639-2/T.</param>
+ /// <returns>Whether the language could be converted.</returns>
+ public bool TryGetISO6392TFromB(string isoB, [NotNullWhen(true)] out string? isoT);
}
diff --git a/MediaBrowser.Model/IO/AsyncFile.cs b/MediaBrowser.Model/IO/AsyncFile.cs
index 3c8007d1c..a9db6b81c 100644
--- a/MediaBrowser.Model/IO/AsyncFile.cs
+++ b/MediaBrowser.Model/IO/AsyncFile.cs
@@ -27,6 +27,14 @@ namespace MediaBrowser.Model.IO
};
/// <summary>
+ /// Creates, or truncates and overwrites, a file in the specified path.
+ /// </summary>
+ /// <param name="path">The path and name of the file to create.</param>
+ /// <returns>A <see cref="FileStream" /> that provides read/write access to the file specified in path.</returns>
+ public static FileStream Create(string path)
+ => new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+
+ /// <summary>
/// Opens an existing file for reading.
/// </summary>
/// <param name="path">The file to be opened for reading.</param>
diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs
index 2085328dd..0ed2e30d5 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,13 +153,41 @@ 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);
+ /// <summary>
+ /// Gets the files.
+ /// </summary>
+ /// <param name="path">The path in which to search.</param>
+ /// <param name="searchPattern">The search string to match against the names of files. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but it doesn't support regular expressions.</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, string searchPattern, bool recursive = false);
+
+ /// <summary>
+ /// Gets the files.
+ /// </summary>
+ /// <param name="path">The path in which to search.</param>
+ /// <param name="extensions">The file extensions to search for.</param>
+ /// <param name="enableCaseSensitiveExtensions">Enable case-sensitive check for extensions.</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, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive);
/// <summary>
+ /// Gets the files.
+ /// </summary>
+ /// <param name="path">The path in which to search.</param>
+ /// <param name="searchPattern">The search string to match against the names of files. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but it doesn't support regular expressions.</param>
+ /// <param name="extensions">The file extensions to search for.</param>
+ /// <param name="enableCaseSensitiveExtensions">Enable case-sensitive check for extensions.</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, string searchPattern, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive);
+
+ /// <summary>
/// Gets the file system entries.
/// </summary>
/// <param name="path">The path.</param>
diff --git a/MediaBrowser.Model/Library/UserViewQuery.cs b/MediaBrowser.Model/Library/UserViewQuery.cs
index 643a1f9b1..01d5e3b6c 100644
--- a/MediaBrowser.Model/Library/UserViewQuery.cs
+++ b/MediaBrowser.Model/Library/UserViewQuery.cs
@@ -1,8 +1,8 @@
#pragma warning disable CS1591
using System;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
namespace MediaBrowser.Model.Library
{
diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
index d872572b7..38e273176 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
@@ -3,6 +3,7 @@
using System;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
namespace MediaBrowser.Model.LiveTv
{
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
index b26f5f45f..1e8add943 100644
--- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
@@ -83,7 +83,7 @@ namespace MediaBrowser.Model.LiveTv
/// Gets or sets the parent primary image item identifier.
/// </summary>
/// <value>The parent primary image item identifier.</value>
- public string ParentPrimaryImageItemId { get; set; }
+ public Guid? ParentPrimaryImageItemId { get; set; }
/// <summary>
/// Gets or sets the parent primary image tag.
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs
index dae885775..e93ad81d3 100644
--- a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs
@@ -1,6 +1,6 @@
#pragma warning disable CS1591
-using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
namespace MediaBrowser.Model.LiveTv
{
diff --git a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
index a355387b1..b70333bce 100644
--- a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
+++ b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs
@@ -9,6 +9,7 @@ namespace MediaBrowser.Model.LiveTv
{
AllowHWTranscoding = true;
IgnoreDts = true;
+ ReadAtNativeFramerate = false;
AllowStreamSharing = true;
AllowFmp4TranscodingContainer = false;
FallbackMaxStreamingBitrate = 30000000;
@@ -43,5 +44,7 @@ namespace MediaBrowser.Model.LiveTv
public string UserAgent { get; set; }
public bool IgnoreDts { get; set; }
+
+ public bool ReadAtNativeFramerate { get; set; }
}
}
diff --git a/MediaBrowser.Model/Lyrics/LyricLine.cs b/MediaBrowser.Model/Lyrics/LyricLine.cs
index 64d1f64c2..788bace69 100644
--- a/MediaBrowser.Model/Lyrics/LyricLine.cs
+++ b/MediaBrowser.Model/Lyrics/LyricLine.cs
@@ -1,3 +1,5 @@
+using System.Collections.Generic;
+
namespace MediaBrowser.Model.Lyrics;
/// <summary>
@@ -10,10 +12,12 @@ public class LyricLine
/// </summary>
/// <param name="text">The lyric text.</param>
/// <param name="start">The lyric start time in ticks.</param>
- public LyricLine(string text, long? start = null)
+ /// <param name="cues">The time-aligned cues for the song's lyrics.</param>
+ public LyricLine(string text, long? start = null, IReadOnlyList<LyricLineCue>? cues = null)
{
Text = text;
Start = start;
+ Cues = cues;
}
/// <summary>
@@ -25,4 +29,9 @@ public class LyricLine
/// Gets the start time in ticks.
/// </summary>
public long? Start { get; }
+
+ /// <summary>
+ /// Gets the time-aligned cues for the song's lyrics.
+ /// </summary>
+ public IReadOnlyList<LyricLineCue>? Cues { get; }
}
diff --git a/MediaBrowser.Model/Lyrics/LyricLineCue.cs b/MediaBrowser.Model/Lyrics/LyricLineCue.cs
new file mode 100644
index 000000000..1172a0231
--- /dev/null
+++ b/MediaBrowser.Model/Lyrics/LyricLineCue.cs
@@ -0,0 +1,35 @@
+namespace MediaBrowser.Model.Lyrics;
+
+/// <summary>
+/// LyricLineCue model, holds information about the timing of words within a LyricLine.
+/// </summary>
+public class LyricLineCue
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LyricLineCue"/> class.
+ /// </summary>
+ /// <param name="position">The start of the character index of the lyric.</param>
+ /// <param name="start">The start of the timestamp the lyric is synced to in ticks.</param>
+ /// <param name="end">The end of the timestamp the lyric is synced to in ticks.</param>
+ public LyricLineCue(int position, long start, long? end)
+ {
+ Position = position;
+ Start = start;
+ End = end;
+ }
+
+ /// <summary>
+ /// Gets the character index of the lyric.
+ /// </summary>
+ public int Position { get; }
+
+ /// <summary>
+ /// Gets the timestamp the lyric is synced to in ticks.
+ /// </summary>
+ public long Start { get; }
+
+ /// <summary>
+ /// Gets the end timestamp the lyric is synced to in ticks.
+ /// </summary>
+ public long? End { get; }
+}
diff --git a/MediaBrowser.Model/Lyrics/LyricSearchRequest.cs b/MediaBrowser.Model/Lyrics/LyricSearchRequest.cs
index 48c442a55..67f3d7b42 100644
--- a/MediaBrowser.Model/Lyrics/LyricSearchRequest.cs
+++ b/MediaBrowser.Model/Lyrics/LyricSearchRequest.cs
@@ -15,7 +15,12 @@ public class LyricSearchRequest : IHasProviderIds
public string? MediaPath { get; set; }
/// <summary>
- /// Gets or sets the artist name.
+ /// Gets or sets the album artist names.
+ /// </summary>
+ public IReadOnlyList<string>? AlbumArtistsNames { get; set; }
+
+ /// <summary>
+ /// Gets or sets the artist names.
/// </summary>
public IReadOnlyList<string>? ArtistNames { get; set; }
diff --git a/MediaBrowser.Model/MediaInfo/AudioIndexSource.cs b/MediaBrowser.Model/MediaInfo/AudioIndexSource.cs
new file mode 100644
index 000000000..810087b92
--- /dev/null
+++ b/MediaBrowser.Model/MediaInfo/AudioIndexSource.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace MediaBrowser.Model.MediaInfo;
+
+/// <summary>
+/// How is the audio index determined.
+/// </summary>
+[Flags]
+public enum AudioIndexSource
+{
+ /// <summary>
+ /// The default index when no preference is specified.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// The index is calculated whether the track is marked as default or not.
+ /// </summary>
+ Default = 1 << 0,
+
+ /// <summary>
+ /// The index is calculated whether the track is in preferred language or not.
+ /// </summary>
+ Language = 1 << 1,
+
+ /// <summary>
+ /// The index is specified by the user.
+ /// </summary>
+ User = 1 << 2
+}
diff --git a/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs b/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs
index a0433fee1..d9129c395 100644
--- a/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs
+++ b/MediaBrowser.Model/MediaSegments/MediaSegmentDto.cs
@@ -1,5 +1,6 @@
using System;
-using Jellyfin.Data.Enums;
+using System.ComponentModel;
+using Jellyfin.Database.Implementations.Enums;
namespace MediaBrowser.Model.MediaSegments;
@@ -21,6 +22,7 @@ public class MediaSegmentDto
/// <summary>
/// Gets or sets the type of content this segment defines.
/// </summary>
+ [DefaultValue(MediaSegmentType.Unknown)]
public MediaSegmentType Type { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index e4c0239b8..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
@@ -144,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/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/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs
index 1f5163aa8..e7a309924 100644
--- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs
+++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace MediaBrowser.Model.Providers
{
/// <summary>
@@ -13,15 +11,11 @@ namespace MediaBrowser.Model.Providers
/// <param name="name">Name of the external id provider (IE: IMDB, MusicBrainz, etc).</param>
/// <param name="key">Key for this id. This key should be unique across all providers.</param>
/// <param name="type">Specific media type for this id.</param>
- /// <param name="urlFormatString">URL format string.</param>
- public ExternalIdInfo(string name, string key, ExternalIdMediaType? type, string? urlFormatString)
+ public ExternalIdInfo(string name, string key, ExternalIdMediaType? type)
{
Name = name;
Key = key;
Type = type;
-#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11
- UrlFormatString = urlFormatString;
-#pragma warning restore CS0618 // Type or member is obsolete
}
/// <summary>
@@ -46,11 +40,5 @@ namespace MediaBrowser.Model.Providers
/// This can be used along with the <see cref="Name"/> to localize the external id on the client.
/// </remarks>
public ExternalIdMediaType? Type { get; set; }
-
- /// <summary>
- /// Gets or sets the URL format string.
- /// </summary>
- [Obsolete("Obsolete in 10.10, to be removed in 10.11")]
- public string? UrlFormatString { get; set; }
}
}
diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs
index ef518369c..71a131bb8 100644
--- a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs
+++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs
@@ -71,6 +71,11 @@ namespace MediaBrowser.Model.Providers
/// <summary>
/// A book.
/// </summary>
- Book = 13
+ Book = 13,
+
+ /// <summary>
+ /// A music recording.
+ /// </summary>
+ Recording = 14
}
}
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index 49d7c0bcb..ffecd392f 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -1,7 +1,3 @@
-#pragma warning disable CS1591
-
-using System;
-
namespace MediaBrowser.Model.Querying
{
/// <summary>
@@ -39,6 +35,9 @@ namespace MediaBrowser.Model.Querying
/// </summary>
Trickplay,
+ /// <summary>
+ /// The child count.
+ /// </summary>
ChildCount,
/// <summary>
@@ -82,11 +81,6 @@ namespace MediaBrowser.Model.Querying
Genres,
/// <summary>
- /// The home page URL.
- /// </summary>
- HomePageUrl,
-
- /// <summary>
/// The item counts.
/// </summary>
ItemCounts,
@@ -101,6 +95,9 @@ namespace MediaBrowser.Model.Querying
/// </summary>
MediaSources,
+ /// <summary>
+ /// The original title.
+ /// </summary>
OriginalTitle,
/// <summary>
@@ -123,6 +120,9 @@ namespace MediaBrowser.Model.Querying
/// </summary>
People,
+ /// <summary>
+ /// Value indicating whether playback access is granted.
+ /// </summary>
PlayAccess,
/// <summary>
@@ -140,6 +140,9 @@ namespace MediaBrowser.Model.Querying
/// </summary>
PrimaryImageAspectRatio,
+ /// <summary>
+ /// The recursive item count.
+ /// </summary>
RecursiveItemCount,
/// <summary>
@@ -148,14 +151,6 @@ namespace MediaBrowser.Model.Querying
Settings,
/// <summary>
- /// The screenshot image tags.
- /// </summary>
- [Obsolete("Screenshot image type is no longer used.")]
- ScreenshotImageTags,
-
- SeriesPrimaryImage,
-
- /// <summary>
/// The series studio.
/// </summary>
SeriesStudio,
@@ -201,26 +196,58 @@ namespace MediaBrowser.Model.Querying
SeasonUserData,
/// <summary>
- /// The service name.
+ /// The last time metadata was refreshed.
/// </summary>
- ServiceName,
- ThemeSongIds,
- ThemeVideoIds,
- ExternalEtag,
- PresentationUniqueKey,
- InheritedParentalRatingValue,
- ExternalSeriesId,
- SeriesPresentationUniqueKey,
DateLastRefreshed,
+
+ /// <summary>
+ /// The last time metadata was saved.
+ /// </summary>
DateLastSaved,
+
+ /// <summary>
+ /// The refresh state.
+ /// </summary>
RefreshState,
+
+ /// <summary>
+ /// The channel image.
+ /// </summary>
ChannelImage,
+
+ /// <summary>
+ /// Value indicating whether media source display is enabled.
+ /// </summary>
EnableMediaSourceDisplay,
+
+ /// <summary>
+ /// The width.
+ /// </summary>
Width,
+
+ /// <summary>
+ /// The height.
+ /// </summary>
Height,
+
+ /// <summary>
+ /// The external Ids.
+ /// </summary>
ExtraIds,
+
+ /// <summary>
+ /// The local trailer count.
+ /// </summary>
LocalTrailerCount,
+
+ /// <summary>
+ /// Value indicating whether the item is HD.
+ /// </summary>
IsHD,
+
+ /// <summary>
+ /// The special feature count.
+ /// </summary>
SpecialFeatureCount
}
}
diff --git a/MediaBrowser.Model/Querying/LatestItemsQuery.cs b/MediaBrowser.Model/Querying/LatestItemsQuery.cs
index 251ff5d68..40dc81397 100644
--- a/MediaBrowser.Model/Querying/LatestItemsQuery.cs
+++ b/MediaBrowser.Model/Querying/LatestItemsQuery.cs
@@ -2,8 +2,8 @@
#pragma warning disable CS1591
using System;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Querying
diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs
index 8dece28a0..a2a3a9d1b 100644
--- a/MediaBrowser.Model/Querying/NextUpQuery.cs
+++ b/MediaBrowser.Model/Querying/NextUpQuery.cs
@@ -1,79 +1,72 @@
#pragma warning disable CS1591
using System;
-using Jellyfin.Data.Entities;
+using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Model.Entities;
-namespace MediaBrowser.Model.Querying
+namespace MediaBrowser.Model.Querying;
+
+public class NextUpQuery
{
- public class NextUpQuery
+ public NextUpQuery()
{
- public NextUpQuery()
- {
- EnableImageTypes = Array.Empty<ImageType>();
- EnableTotalRecordCount = true;
- DisableFirstEpisode = false;
- NextUpDateCutoff = DateTime.MinValue;
- EnableResumable = false;
- EnableRewatching = false;
- }
-
- /// <summary>
- /// Gets or sets the user.
- /// </summary>
- /// <value>The user.</value>
- public required User User { get; set; }
+ EnableImageTypes = Array.Empty<ImageType>();
+ EnableTotalRecordCount = true;
+ NextUpDateCutoff = DateTime.MinValue;
+ EnableResumable = false;
+ EnableRewatching = false;
+ }
- /// <summary>
- /// Gets or sets the parent identifier.
- /// </summary>
- /// <value>The parent identifier.</value>
- public Guid? ParentId { get; set; }
+ /// <summary>
+ /// Gets or sets the user.
+ /// </summary>
+ /// <value>The user.</value>
+ public required User User { get; set; }
- /// <summary>
- /// Gets or sets the series id.
- /// </summary>
- /// <value>The series id.</value>
- public Guid? SeriesId { get; set; }
+ /// <summary>
+ /// Gets or sets the parent identifier.
+ /// </summary>
+ /// <value>The parent identifier.</value>
+ public Guid? ParentId { get; set; }
- /// <summary>
- /// Gets or sets the start index. Use for paging.
- /// </summary>
- /// <value>The start index.</value>
- public int? StartIndex { get; set; }
+ /// <summary>
+ /// Gets or sets the series id.
+ /// </summary>
+ /// <value>The series id.</value>
+ public Guid? SeriesId { get; set; }
- /// <summary>
- /// Gets or sets the maximum number of items to return.
- /// </summary>
- /// <value>The limit.</value>
- public int? Limit { get; set; }
+ /// <summary>
+ /// Gets or sets the start index. Use for paging.
+ /// </summary>
+ /// <value>The start index.</value>
+ public int? StartIndex { get; set; }
- /// <summary>
- /// Gets or sets the enable image types.
- /// </summary>
- /// <value>The enable image types.</value>
- public ImageType[] EnableImageTypes { get; set; }
+ /// <summary>
+ /// Gets or sets the maximum number of items to return.
+ /// </summary>
+ /// <value>The limit.</value>
+ public int? Limit { get; set; }
- public bool EnableTotalRecordCount { get; set; }
+ /// <summary>
+ /// Gets or sets the enable image types.
+ /// </summary>
+ /// <value>The enable image types.</value>
+ public ImageType[] EnableImageTypes { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether do disable sending first episode as next up.
- /// </summary>
- public bool DisableFirstEpisode { get; set; }
+ public bool EnableTotalRecordCount { get; set; }
- /// <summary>
- /// Gets or sets a value indicating the oldest date for a show to appear in Next Up.
- /// </summary>
- public DateTime NextUpDateCutoff { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating the oldest date for a show to appear in Next Up.
+ /// </summary>
+ public DateTime NextUpDateCutoff { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether to include resumable episodes as next up.
- /// </summary>
- public bool EnableResumable { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether to include resumable episodes as next up.
+ /// </summary>
+ public bool EnableResumable { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether getting rewatching next up list.
- /// </summary>
- public bool EnableRewatching { get; set; }
- }
+ /// <summary>
+ /// Gets or sets a value indicating whether getting rewatching next up list.
+ /// </summary>
+ public bool EnableRewatching { get; set; }
}
diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs
index 2e2979fcf..a18a813cc 100644
--- a/MediaBrowser.Model/Search/SearchHint.cs
+++ b/MediaBrowser.Model/Search/SearchHint.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using Jellyfin.Data.Enums;
namespace MediaBrowser.Model.Search
@@ -115,6 +116,7 @@ namespace MediaBrowser.Model.Search
/// Gets or sets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
+ [DefaultValue(MediaType.Unknown)]
public MediaType MediaType { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs
index 39c5ac8fa..902bab9a6 100644
--- a/MediaBrowser.Model/Session/TranscodeReason.cs
+++ b/MediaBrowser.Model/Session/TranscodeReason.cs
@@ -14,6 +14,7 @@ namespace MediaBrowser.Model.Session
SubtitleCodecNotSupported = 1 << 3,
AudioIsExternal = 1 << 4,
SecondaryAudioNotSupported = 1 << 5,
+ StreamCountExceedsLimit = 1 << 26,
// Video Constraints
VideoProfileNotSupported = 1 << 6,
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/SyncPlay/GroupUpdate.cs b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs
index ec67d7ea8..794443499 100644
--- a/MediaBrowser.Model/SyncPlay/GroupUpdate.cs
+++ b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs
@@ -5,15 +5,18 @@ namespace MediaBrowser.Model.SyncPlay;
/// <summary>
/// Group update without data.
/// </summary>
-public abstract class GroupUpdate
+/// <typeparam name="T">The type of the update data.</typeparam>
+public abstract class GroupUpdate<T>
{
/// <summary>
- /// Initializes a new instance of the <see cref="GroupUpdate"/> class.
+ /// Initializes a new instance of the <see cref="GroupUpdate{T}"/> class.
/// </summary>
/// <param name="groupId">The group identifier.</param>
- protected GroupUpdate(Guid groupId)
+ /// <param name="data">The update data.</param>
+ protected GroupUpdate(Guid groupId, T data)
{
GroupId = groupId;
+ Data = data;
}
/// <summary>
@@ -23,8 +26,14 @@ public abstract class GroupUpdate
public Guid GroupId { get; }
/// <summary>
+ /// Gets the update data.
+ /// </summary>
+ /// <value>The update data.</value>
+ public T Data { get; }
+
+ /// <summary>
/// Gets the update type.
/// </summary>
/// <value>The update type.</value>
- public GroupUpdateType Type { get; init; }
+ public abstract GroupUpdateType Type { get; }
}
diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdateOfT.cs b/MediaBrowser.Model/SyncPlay/GroupUpdateOfT.cs
deleted file mode 100644
index 25cd44461..000000000
--- a/MediaBrowser.Model/SyncPlay/GroupUpdateOfT.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma warning disable SA1649
-
-using System;
-
-namespace MediaBrowser.Model.SyncPlay;
-
-/// <summary>
-/// Class GroupUpdate.
-/// </summary>
-/// <typeparam name="T">The type of the data of the message.</typeparam>
-public class GroupUpdate<T> : GroupUpdate
-{
- /// <summary>
- /// Initializes a new instance of the <see cref="GroupUpdate{T}"/> class.
- /// </summary>
- /// <param name="groupId">The group identifier.</param>
- /// <param name="type">The update type.</param>
- /// <param name="data">The update data.</param>
- public GroupUpdate(Guid groupId, GroupUpdateType type, T data)
- : base(groupId)
- {
- Data = data;
- Type = type;
- }
-
- /// <summary>
- /// Gets the update data.
- /// </summary>
- /// <value>The update data.</value>
- public T Data { get; }
-}
diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs
index 907d1defe..e792229a4 100644
--- a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs
+++ b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs
@@ -46,16 +46,6 @@ namespace MediaBrowser.Model.SyncPlay
GroupDoesNotExist,
/// <summary>
- /// The create-group-denied error. Sent when a user tries to create a group without required permissions.
- /// </summary>
- CreateGroupDenied,
-
- /// <summary>
- /// The join-group-denied error. Sent when a user tries to join a group without required permissions.
- /// </summary>
- JoinGroupDenied,
-
- /// <summary>
/// The library-access-denied error. Sent when a user tries to join a group without required access to the library.
/// </summary>
LibraryAccessDenied
diff --git a/MediaBrowser.Model/SyncPlay/SyncPlayGroupDoesNotExistUpdate.cs b/MediaBrowser.Model/SyncPlay/SyncPlayGroupDoesNotExistUpdate.cs
new file mode 100644
index 000000000..7e2d10c8b
--- /dev/null
+++ b/MediaBrowser.Model/SyncPlay/SyncPlayGroupDoesNotExistUpdate.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayGroupDoesNotExistUpdate : GroupUpdate<string>
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayGroupDoesNotExistUpdate"/> class.
+ /// </summary>
+ /// <param name="groupId">The groupId.</param>
+ /// <param name="data">The data.</param>
+ public SyncPlayGroupDoesNotExistUpdate(Guid groupId, string data) : base(groupId, data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(GroupUpdateType.GroupDoesNotExist)]
+ public override GroupUpdateType Type => GroupUpdateType.GroupDoesNotExist;
+}
diff --git a/MediaBrowser.Model/SyncPlay/SyncPlayGroupJoinedUpdate.cs b/MediaBrowser.Model/SyncPlay/SyncPlayGroupJoinedUpdate.cs
new file mode 100644
index 000000000..bfb49152a
--- /dev/null
+++ b/MediaBrowser.Model/SyncPlay/SyncPlayGroupJoinedUpdate.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayGroupJoinedUpdate : GroupUpdate<GroupInfoDto>
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayGroupJoinedUpdate"/> class.
+ /// </summary>
+ /// <param name="groupId">The groupId.</param>
+ /// <param name="data">The data.</param>
+ public SyncPlayGroupJoinedUpdate(Guid groupId, GroupInfoDto data) : base(groupId, data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(GroupUpdateType.GroupJoined)]
+ public override GroupUpdateType Type => GroupUpdateType.GroupJoined;
+}
diff --git a/MediaBrowser.Model/SyncPlay/SyncPlayGroupLeftUpdate.cs b/MediaBrowser.Model/SyncPlay/SyncPlayGroupLeftUpdate.cs
new file mode 100644
index 000000000..5ff60c5c2
--- /dev/null
+++ b/MediaBrowser.Model/SyncPlay/SyncPlayGroupLeftUpdate.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayGroupLeftUpdate : GroupUpdate<string>
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayGroupLeftUpdate"/> class.
+ /// </summary>
+ /// <param name="groupId">The groupId.</param>
+ /// <param name="data">The data.</param>
+ public SyncPlayGroupLeftUpdate(Guid groupId, string data) : base(groupId, data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(GroupUpdateType.GroupLeft)]
+ public override GroupUpdateType Type => GroupUpdateType.GroupLeft;
+}
diff --git a/MediaBrowser.Model/SyncPlay/SyncPlayLibraryAccessDeniedUpdate.cs b/MediaBrowser.Model/SyncPlay/SyncPlayLibraryAccessDeniedUpdate.cs
new file mode 100644
index 000000000..0d9a722f7
--- /dev/null
+++ b/MediaBrowser.Model/SyncPlay/SyncPlayLibraryAccessDeniedUpdate.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayLibraryAccessDeniedUpdate : GroupUpdate<string>
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayLibraryAccessDeniedUpdate"/> class.
+ /// </summary>
+ /// <param name="groupId">The groupId.</param>
+ /// <param name="data">The data.</param>
+ public SyncPlayLibraryAccessDeniedUpdate(Guid groupId, string data) : base(groupId, data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(GroupUpdateType.LibraryAccessDenied)]
+ public override GroupUpdateType Type => GroupUpdateType.LibraryAccessDenied;
+}
diff --git a/MediaBrowser.Model/SyncPlay/SyncPlayNotInGroupUpdate.cs b/MediaBrowser.Model/SyncPlay/SyncPlayNotInGroupUpdate.cs
new file mode 100644
index 000000000..a3b610f61
--- /dev/null
+++ b/MediaBrowser.Model/SyncPlay/SyncPlayNotInGroupUpdate.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayNotInGroupUpdate : GroupUpdate<string>
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayNotInGroupUpdate"/> class.
+ /// </summary>
+ /// <param name="groupId">The groupId.</param>
+ /// <param name="data">The data.</param>
+ public SyncPlayNotInGroupUpdate(Guid groupId, string data) : base(groupId, data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(GroupUpdateType.NotInGroup)]
+ public override GroupUpdateType Type => GroupUpdateType.NotInGroup;
+}
diff --git a/MediaBrowser.Model/SyncPlay/SyncPlayPlayQueueUpdate.cs b/MediaBrowser.Model/SyncPlay/SyncPlayPlayQueueUpdate.cs
new file mode 100644
index 000000000..83d9bd40b
--- /dev/null
+++ b/MediaBrowser.Model/SyncPlay/SyncPlayPlayQueueUpdate.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayPlayQueueUpdate : GroupUpdate<PlayQueueUpdate>
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayPlayQueueUpdate"/> class.
+ /// </summary>
+ /// <param name="groupId">The groupId.</param>
+ /// <param name="data">The data.</param>
+ public SyncPlayPlayQueueUpdate(Guid groupId, PlayQueueUpdate data) : base(groupId, data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(GroupUpdateType.PlayQueue)]
+ public override GroupUpdateType Type => GroupUpdateType.PlayQueue;
+}
diff --git a/MediaBrowser.Model/SyncPlay/SyncPlayStateUpdate.cs b/MediaBrowser.Model/SyncPlay/SyncPlayStateUpdate.cs
new file mode 100644
index 000000000..744ca46a0
--- /dev/null
+++ b/MediaBrowser.Model/SyncPlay/SyncPlayStateUpdate.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayStateUpdate : GroupUpdate<GroupStateUpdate>
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayStateUpdate"/> class.
+ /// </summary>
+ /// <param name="groupId">The groupId.</param>
+ /// <param name="data">The data.</param>
+ public SyncPlayStateUpdate(Guid groupId, GroupStateUpdate data) : base(groupId, data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(GroupUpdateType.StateUpdate)]
+ public override GroupUpdateType Type => GroupUpdateType.StateUpdate;
+}
diff --git a/MediaBrowser.Model/SyncPlay/SyncPlayUserJoinedUpdate.cs b/MediaBrowser.Model/SyncPlay/SyncPlayUserJoinedUpdate.cs
new file mode 100644
index 000000000..e8c6b4df4
--- /dev/null
+++ b/MediaBrowser.Model/SyncPlay/SyncPlayUserJoinedUpdate.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayUserJoinedUpdate : GroupUpdate<string>
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayUserJoinedUpdate"/> class.
+ /// </summary>
+ /// <param name="groupId">The groupId.</param>
+ /// <param name="data">The data.</param>
+ public SyncPlayUserJoinedUpdate(Guid groupId, string data) : base(groupId, data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(GroupUpdateType.UserJoined)]
+ public override GroupUpdateType Type => GroupUpdateType.UserJoined;
+}
diff --git a/MediaBrowser.Model/SyncPlay/SyncPlayUserLeftUpdate.cs b/MediaBrowser.Model/SyncPlay/SyncPlayUserLeftUpdate.cs
new file mode 100644
index 000000000..97be8e63a
--- /dev/null
+++ b/MediaBrowser.Model/SyncPlay/SyncPlayUserLeftUpdate.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayUserLeftUpdate : GroupUpdate<string>
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayUserLeftUpdate"/> class.
+ /// </summary>
+ /// <param name="groupId">The groupId.</param>
+ /// <param name="data">The data.</param>
+ public SyncPlayUserLeftUpdate(Guid groupId, string data) : base(groupId, data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(GroupUpdateType.UserLeft)]
+ public override GroupUpdateType Type => GroupUpdateType.UserLeft;
+}
diff --git a/MediaBrowser.Model/System/FolderStorageInfo.cs b/MediaBrowser.Model/System/FolderStorageInfo.cs
new file mode 100644
index 000000000..7b10e4ea5
--- /dev/null
+++ b/MediaBrowser.Model/System/FolderStorageInfo.cs
@@ -0,0 +1,32 @@
+namespace MediaBrowser.Model.System;
+
+/// <summary>
+/// Contains information about a specific folder.
+/// </summary>
+public record FolderStorageInfo
+{
+ /// <summary>
+ /// Gets the path of the folder in question.
+ /// </summary>
+ public required string Path { get; init; }
+
+ /// <summary>
+ /// Gets the free space of the underlying storage device of the <see cref="Path"/>.
+ /// </summary>
+ public long FreeSpace { get; init; }
+
+ /// <summary>
+ /// Gets the used space of the underlying storage device of the <see cref="Path"/>.
+ /// </summary>
+ public long UsedSpace { get; init; }
+
+ /// <summary>
+ /// Gets the kind of storage device of the <see cref="Path"/>.
+ /// </summary>
+ public string? StorageType { get; init; }
+
+ /// <summary>
+ /// Gets the Device Identifier.
+ /// </summary>
+ public string? DeviceId { get; init; }
+}
diff --git a/MediaBrowser.Model/System/LibraryStorageInfo.cs b/MediaBrowser.Model/System/LibraryStorageInfo.cs
new file mode 100644
index 000000000..d4111b29c
--- /dev/null
+++ b/MediaBrowser.Model/System/LibraryStorageInfo.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.System;
+
+/// <summary>
+/// Contains informations about a libraries storage informations.
+/// </summary>
+public class LibraryStorageInfo
+{
+ /// <summary>
+ /// Gets or sets the Library Id.
+ /// </summary>
+ public required Guid Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name of the library.
+ /// </summary>
+ public required string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the storage informations about the folders used in a library.
+ /// </summary>
+ public required IReadOnlyCollection<FolderStorageInfo> Folders { get; set; }
+}
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/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index f37ac6a14..232a2a6bc 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -6,133 +6,139 @@ using System.Collections.Generic;
using System.ComponentModel;
using MediaBrowser.Model.Updates;
-namespace MediaBrowser.Model.System
+namespace MediaBrowser.Model.System;
+
+/// <summary>
+/// Class SystemInfo.
+/// </summary>
+public class SystemInfo : PublicSystemInfo
{
/// <summary>
- /// Class SystemInfo.
+ /// Initializes a new instance of the <see cref="SystemInfo" /> class.
/// </summary>
- public class SystemInfo : PublicSystemInfo
+ public SystemInfo()
{
- /// <summary>
- /// Initializes a new instance of the <see cref="SystemInfo" /> class.
- /// </summary>
- public SystemInfo()
- {
- CompletedInstallations = Array.Empty<InstallationInfo>();
- }
-
- /// <summary>
- /// Gets or sets the display name of the operating system.
- /// </summary>
- /// <value>The display name of the operating system.</value>
- [Obsolete("This is no longer set")]
- public string OperatingSystemDisplayName { get; set; } = string.Empty;
-
- /// <summary>
- /// Gets or sets the package name.
- /// </summary>
- /// <value>The value of the '-package' command line argument.</value>
- public string PackageName { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance has pending restart.
- /// </summary>
- /// <value><c>true</c> if this instance has pending restart; otherwise, <c>false</c>.</value>
- public bool HasPendingRestart { get; set; }
-
- public bool IsShuttingDown { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [supports library monitor].
- /// </summary>
- /// <value><c>true</c> if [supports library monitor]; otherwise, <c>false</c>.</value>
- public bool SupportsLibraryMonitor { get; set; }
-
- /// <summary>
- /// Gets or sets the web socket port number.
- /// </summary>
- /// <value>The web socket port number.</value>
- public int WebSocketPortNumber { get; set; }
-
- /// <summary>
- /// Gets or sets the completed installations.
- /// </summary>
- /// <value>The completed installations.</value>
- public InstallationInfo[] CompletedInstallations { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance can self restart.
- /// </summary>
- /// <value><c>true</c>.</value>
- [Obsolete("This is always true")]
- [DefaultValue(true)]
- public bool CanSelfRestart { get; set; } = true;
-
- [Obsolete("This is always false")]
- [DefaultValue(false)]
- public bool CanLaunchWebBrowser { get; set; } = false;
-
- /// <summary>
- /// Gets or sets the program data path.
- /// </summary>
- /// <value>The program data path.</value>
- public string ProgramDataPath { get; set; }
-
- /// <summary>
- /// Gets or sets the web UI resources path.
- /// </summary>
- /// <value>The web UI resources path.</value>
- public string WebPath { get; set; }
-
- /// <summary>
- /// Gets or sets the items by name path.
- /// </summary>
- /// <value>The items by name path.</value>
- public string ItemsByNamePath { get; set; }
-
- /// <summary>
- /// Gets or sets the cache path.
- /// </summary>
- /// <value>The cache path.</value>
- public string CachePath { get; set; }
-
- /// <summary>
- /// Gets or sets the log path.
- /// </summary>
- /// <value>The log path.</value>
- public string LogPath { get; set; }
-
- /// <summary>
- /// Gets or sets the internal metadata path.
- /// </summary>
- /// <value>The internal metadata path.</value>
- public string InternalMetadataPath { get; set; }
-
- /// <summary>
- /// Gets or sets the transcode path.
- /// </summary>
- /// <value>The transcode path.</value>
- public string TranscodingTempPath { get; set; }
-
- /// <summary>
- /// Gets or sets the list of cast receiver applications.
- /// </summary>
- public IReadOnlyList<CastReceiverApplication> CastReceiverApplications { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance has update available.
- /// </summary>
- /// <value><c>true</c> if this instance has update available; otherwise, <c>false</c>.</value>
- [Obsolete("This should be handled by the package manager")]
- [DefaultValue(false)]
- public bool HasUpdateAvailable { get; set; }
-
- [Obsolete("This isn't set correctly anymore")]
- [DefaultValue("System")]
- public string EncoderLocation { get; set; } = "System";
-
- [Obsolete("This is no longer set")]
- [DefaultValue("X64")]
- public string SystemArchitecture { get; set; } = "X64";
+ CompletedInstallations = Array.Empty<InstallationInfo>();
}
+
+ /// <summary>
+ /// Gets or sets the display name of the operating system.
+ /// </summary>
+ /// <value>The display name of the operating system.</value>
+ [Obsolete("This is no longer set")]
+ public string OperatingSystemDisplayName { get; set; } = string.Empty;
+
+ /// <summary>
+ /// Gets or sets the package name.
+ /// </summary>
+ /// <value>The value of the '-package' command line argument.</value>
+ public string PackageName { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has pending restart.
+ /// </summary>
+ /// <value><c>true</c> if this instance has pending restart; otherwise, <c>false</c>.</value>
+ public bool HasPendingRestart { get; set; }
+
+ public bool IsShuttingDown { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [supports library monitor].
+ /// </summary>
+ /// <value><c>true</c> if [supports library monitor]; otherwise, <c>false</c>.</value>
+ public bool SupportsLibraryMonitor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the web socket port number.
+ /// </summary>
+ /// <value>The web socket port number.</value>
+ public int WebSocketPortNumber { get; set; }
+
+ /// <summary>
+ /// Gets or sets the completed installations.
+ /// </summary>
+ /// <value>The completed installations.</value>
+ public InstallationInfo[] CompletedInstallations { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance can self restart.
+ /// </summary>
+ /// <value><c>true</c>.</value>
+ [Obsolete("This is always true")]
+ [DefaultValue(true)]
+ public bool CanSelfRestart { get; set; } = true;
+
+ [Obsolete("This is always false")]
+ [DefaultValue(false)]
+ public bool CanLaunchWebBrowser { get; set; } = false;
+
+ /// <summary>
+ /// Gets or sets the program data path.
+ /// </summary>
+ /// <value>The program data path.</value>
+ [Obsolete("Use the newer SystemStorageDto instead")]
+ public string ProgramDataPath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the web UI resources path.
+ /// </summary>
+ /// <value>The web UI resources path.</value>
+ [Obsolete("Use the newer SystemStorageDto instead")]
+ public string WebPath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the items by name path.
+ /// </summary>
+ /// <value>The items by name path.</value>
+ [Obsolete("Use the newer SystemStorageDto instead")]
+ public string ItemsByNamePath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the cache path.
+ /// </summary>
+ /// <value>The cache path.</value>
+ [Obsolete("Use the newer SystemStorageDto instead")]
+ public string CachePath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the log path.
+ /// </summary>
+ /// <value>The log path.</value>
+ [Obsolete("Use the newer SystemStorageDto instead")]
+ public string LogPath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the internal metadata path.
+ /// </summary>
+ /// <value>The internal metadata path.</value>
+ [Obsolete("Use the newer SystemStorageDto instead")]
+ public string InternalMetadataPath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the transcode path.
+ /// </summary>
+ /// <value>The transcode path.</value>
+ [Obsolete("Use the newer SystemStorageDto instead")]
+ public string TranscodingTempPath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of cast receiver applications.
+ /// </summary>
+ public IReadOnlyList<CastReceiverApplication> CastReceiverApplications { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has update available.
+ /// </summary>
+ /// <value><c>true</c> if this instance has update available; otherwise, <c>false</c>.</value>
+ [Obsolete("This should be handled by the package manager")]
+ [DefaultValue(false)]
+ public bool HasUpdateAvailable { get; set; }
+
+ [Obsolete("This isn't set correctly anymore")]
+ [DefaultValue("System")]
+ public string EncoderLocation { get; set; } = "System";
+
+ [Obsolete("This is no longer set")]
+ [DefaultValue("X64")]
+ public string SystemArchitecture { get; set; } = "X64";
}
diff --git a/MediaBrowser.Model/System/SystemStorageInfo.cs b/MediaBrowser.Model/System/SystemStorageInfo.cs
new file mode 100644
index 000000000..42e7a37e0
--- /dev/null
+++ b/MediaBrowser.Model/System/SystemStorageInfo.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.System;
+
+/// <summary>
+/// Contains informations about the systems storage.
+/// </summary>
+public class SystemStorageInfo
+{
+ /// <summary>
+ /// Gets or sets the program data path.
+ /// </summary>
+ /// <value>The program data path.</value>
+ public required FolderStorageInfo ProgramDataFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the web UI resources path.
+ /// </summary>
+ /// <value>The web UI resources path.</value>
+ public required FolderStorageInfo WebFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the items by name path.
+ /// </summary>
+ /// <value>The items by name path.</value>
+ public required FolderStorageInfo ImageCacheFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the cache path.
+ /// </summary>
+ /// <value>The cache path.</value>
+ public required FolderStorageInfo CacheFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the log path.
+ /// </summary>
+ /// <value>The log path.</value>
+ public required FolderStorageInfo LogFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the internal metadata path.
+ /// </summary>
+ /// <value>The internal metadata path.</value>
+ public required FolderStorageInfo InternalMetadataFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the transcode path.
+ /// </summary>
+ /// <value>The transcode path.</value>
+ public required FolderStorageInfo TranscodingTempFolder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the storage informations of all libraries.
+ /// </summary>
+ public required IReadOnlyCollection<LibraryStorageInfo> Libraries { get; set; }
+}
diff --git a/MediaBrowser.Model/System/WakeOnLanInfo.cs b/MediaBrowser.Model/System/WakeOnLanInfo.cs
deleted file mode 100644
index aba19a6ba..000000000
--- a/MediaBrowser.Model/System/WakeOnLanInfo.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System.Net.NetworkInformation;
-
-namespace MediaBrowser.Model.System
-{
- /// <summary>
- /// Provides the MAC address and port for wake-on-LAN functionality.
- /// </summary>
- public class WakeOnLanInfo
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class.
- /// </summary>
- /// <param name="macAddress">The MAC address.</param>
- public WakeOnLanInfo(PhysicalAddress macAddress) : this(macAddress.ToString())
- {
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class.
- /// </summary>
- /// <param name="macAddress">The MAC address.</param>
- public WakeOnLanInfo(string macAddress) : this()
- {
- MacAddress = macAddress;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class.
- /// </summary>
- public WakeOnLanInfo()
- {
- Port = 9;
- }
-
- /// <summary>
- /// Gets the MAC address of the device.
- /// </summary>
- /// <value>The MAC address.</value>
- public string? MacAddress { get; }
-
- /// <summary>
- /// Gets or sets the wake-on-LAN port.
- /// </summary>
- /// <value>The wake-on-LAN port.</value>
- public int Port { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 951e05763..2c393ca86 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -6,7 +6,8 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Xml.Serialization;
using Jellyfin.Data.Enums;
-using AccessSchedule = Jellyfin.Data.Entities.AccessSchedule;
+using Jellyfin.Database.Implementations.Enums;
+using AccessSchedule = Jellyfin.Database.Implementations.Entities.AccessSchedule;
namespace MediaBrowser.Model.Users
{
@@ -110,6 +111,8 @@ namespace MediaBrowser.Model.Users
/// <value>The max parental rating.</value>
public int? MaxParentalRating { get; set; }
+ public int? MaxParentalSubRating { get; set; }
+
public string[] BlockedTags { get; set; }
public string[] AllowedTags { get; set; }