diff options
Diffstat (limited to 'MediaBrowser.Model')
| -rw-r--r-- | MediaBrowser.Model/Configuration/TrickplayOptions.cs | 6 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dlna/StreamBuilder.cs | 88 | ||||
| -rw-r--r-- | MediaBrowser.Model/Entities/MediaStream.cs | 36 | ||||
| -rw-r--r-- | MediaBrowser.Model/Entities/ProviderIdsExtensions.cs | 60 | ||||
| -rw-r--r-- | MediaBrowser.Model/Providers/ExternalIdInfo.cs | 5 | ||||
| -rw-r--r-- | MediaBrowser.Model/Tasks/TaskTriggerInfo.cs | 1 |
6 files changed, 171 insertions, 25 deletions
diff --git a/MediaBrowser.Model/Configuration/TrickplayOptions.cs b/MediaBrowser.Model/Configuration/TrickplayOptions.cs index a151d3429b..578bb306a0 100644 --- a/MediaBrowser.Model/Configuration/TrickplayOptions.cs +++ b/MediaBrowser.Model/Configuration/TrickplayOptions.cs @@ -19,6 +19,12 @@ public class TrickplayOptions public bool EnableHwEncoding { get; set; } = false; /// <summary> + /// Gets or sets a value indicating whether to only extract key frames. + /// Significantly faster, but is not compatible with all decoders and/or video files. + /// </summary> + public bool EnableKeyFrameOnlyExtraction { get; set; } = false; + + /// <summary> /// Gets or sets the behavior used by trickplay provider on library scan/update. /// </summary> public TrickplayScanBehavior ScanBehavior { get; set; } = TrickplayScanBehavior.NonBlocking; diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index ba958c0304..d37528ede8 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Model.Dlna private readonly ILogger _logger; private readonly ITranscoderSupport _transcoderSupport; - private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc", "av1" }; + private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc", "vp9", "av1" }; private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" }; private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" }; @@ -108,7 +108,7 @@ namespace MediaBrowser.Model.Dlna var inputAudioSampleRate = audioStream?.SampleRate; var inputAudioBitDepth = audioStream?.BitDepth; - if (directPlayMethod.HasValue) + if (directPlayMethod is PlayMethod.DirectPlay) { var profile = options.Profile; var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true); @@ -124,6 +124,46 @@ namespace MediaBrowser.Model.Dlna } } + if (directPlayMethod is PlayMethod.DirectStream) + { + var remuxContainer = item.TranscodingContainer ?? "ts"; + var supportedHlsContainers = new[] { "ts", "mp4" }; + // If the container specified for the profile is an HLS supported container, use that container instead, overriding the preference + // The client should be responsible to ensure this container is compatible + remuxContainer = Array.Exists(supportedHlsContainers, element => string.Equals(element, directPlayInfo.Profile?.Container, StringComparison.OrdinalIgnoreCase)) ? directPlayInfo.Profile?.Container : remuxContainer; + bool codeIsSupported; + if (item.TranscodingSubProtocol == MediaStreamProtocol.hls) + { + // Enforce HLS audio codec restrictions + if (string.Equals(remuxContainer, "mp4", StringComparison.OrdinalIgnoreCase)) + { + codeIsSupported = _supportedHlsAudioCodecsMp4.Contains(directPlayInfo.Profile?.AudioCodec ?? directPlayInfo.Profile?.Container); + } + else + { + codeIsSupported = _supportedHlsAudioCodecsTs.Contains(directPlayInfo.Profile?.AudioCodec ?? directPlayInfo.Profile?.Container); + } + } + else + { + // Let's assume the client has given a correct container for http + codeIsSupported = true; + } + + if (codeIsSupported) + { + playlistItem.PlayMethod = directPlayMethod.Value; + playlistItem.Container = remuxContainer; + playlistItem.TranscodeReasons = transcodeReasons; + playlistItem.SubProtocol = item.TranscodingSubProtocol; + item.TranscodingContainer = remuxContainer; + return playlistItem; + } + + transcodeReasons |= TranscodeReason.AudioCodecNotSupported; + playlistItem.TranscodeReasons = transcodeReasons; + } + TranscodingProfile? transcodingProfile = null; foreach (var tcProfile in options.Profile.TranscodingProfiles) { @@ -379,6 +419,7 @@ namespace MediaBrowser.Model.Dlna var directPlayProfile = options.Profile.DirectPlayProfiles .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream)); + TranscodeReason transcodeReasons = 0; if (directPlayProfile is null) { _logger.LogDebug( @@ -387,14 +428,25 @@ namespace MediaBrowser.Model.Dlna item.Path ?? "Unknown path", audioStream.Codec ?? "Unknown codec"); - return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); - } + var directStreamProfile = options.Profile.DirectPlayProfiles + .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectStreamSupported(x, item, audioStream)); - TranscodeReason transcodeReasons = 0; + if (directStreamProfile is not null) + { + directPlayProfile = directStreamProfile; + transcodeReasons |= TranscodeReason.ContainerNotSupported; + } + else + { + return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); + } + } // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play - if (item.SupportsDirectPlay) + // Note: As of 10.10 codebase, SupportsDirectPlay is always true because the MediaSourceInfo initializes this key as true + // Need to check additionally for current transcode reasons + if (item.SupportsDirectPlay && transcodeReasons == 0) { if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0)) { @@ -414,7 +466,10 @@ namespace MediaBrowser.Model.Dlna { if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0)) { - if (options.EnableDirectStream) + // Note: as of 10.10 codebase, the options.EnableDirectStream is always false due to + // "direct-stream http streaming is currently broken" + // Don't check that option for audio as we always assume that is supported + if (transcodeReasons == TranscodeReason.ContainerNotSupported) { return (directPlayProfile, PlayMethod.DirectStream, transcodeReasons); } @@ -2130,5 +2185,24 @@ namespace MediaBrowser.Model.Dlna return true; } + + private static bool IsAudioDirectStreamSupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) + { + // Check container type, this should NOT be supported + // If the container is supported, the file should be directly played + if (!profile.SupportsContainer(item.Container)) + { + // 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; + } } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index e1082adead..dcb3febbd2 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using System.Globalization; using System.Linq; using System.Text; +using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Model.Dlna; @@ -585,6 +586,33 @@ namespace MediaBrowser.Model.Entities } } + [JsonIgnore] + public bool IsPgsSubtitleStream + { + get + { + if (Type != MediaStreamType.Subtitle) + { + return false; + } + + if (string.IsNullOrEmpty(Codec) && !IsExternal) + { + return false; + } + + return IsPgsFormat(Codec); + } + } + + /// <summary> + /// Gets a value indicating whether this is a subtitle steam that is extractable by ffmpeg. + /// All text-based and pgs subtitles can be extracted. + /// </summary> + /// <value><c>true</c> if this is a extractable subtitle steam otherwise, <c>false</c>.</value> + [JsonIgnore] + public bool IsExtractableSubtitleStream => IsTextSubtitleStream || IsPgsSubtitleStream; + /// <summary> /// Gets or sets a value indicating whether [supports external stream]. /// </summary> @@ -666,6 +694,14 @@ namespace MediaBrowser.Model.Entities && !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase)); } + public static bool IsPgsFormat(string format) + { + string codec = format ?? string.Empty; + + return codec.Contains("pgs", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase); + } + public bool SupportsSubtitleConversionTo(string toCodec) { if (!IsTextSubtitleStream) diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index cf453d62cb..1c73091f0d 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -111,31 +111,32 @@ namespace MediaBrowser.Model.Entities /// Sets a provider id. /// </summary> /// <param name="instance">The instance.</param> - /// <param name="name">The name.</param> + /// <param name="name">The name, this should not contain a '=' character.</param> /// <param name="value">The value.</param> - public static void SetProviderId(this IHasProviderIds instance, string name, string? value) + /// <remarks>Due to how deserialization from the database works the name can not contain '='.</remarks> + public static void SetProviderId(this IHasProviderIds instance, string name, string value) { ArgumentNullException.ThrowIfNull(instance); + ArgumentException.ThrowIfNullOrEmpty(name); + ArgumentException.ThrowIfNullOrEmpty(value); + + // When name contains a '=' it can't be deserialized from the database + if (name.Contains('=', StringComparison.Ordinal)) + { + throw new ArgumentException("Provider id name cannot contain '='", nameof(name)); + } + + // Ensure it exists + instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - // If it's null remove the key from the dictionary - if (string.IsNullOrEmpty(value)) + // Match on internal MetadataProvider enum string values before adding arbitrary providers + if (_metadataProviderEnumDictionary.TryGetValue(name, out var enumValue)) { - instance.ProviderIds?.Remove(name); + instance.ProviderIds[enumValue] = value; } else { - // Ensure it exists - instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - // Match on internal MetadataProvider enum string values before adding arbitrary providers - if (_metadataProviderEnumDictionary.TryGetValue(name, out var enumValue)) - { - instance.ProviderIds[enumValue] = value; - } - else - { - instance.ProviderIds[name] = value; - } + instance.ProviderIds[name] = value; } } @@ -149,5 +150,30 @@ namespace MediaBrowser.Model.Entities { instance.SetProviderId(provider.ToString(), value); } + + /// <summary> + /// Removes a provider id. + /// </summary> + /// <param name="instance">The instance.</param> + /// <param name="name">The name.</param> + public static void RemoveProviderId(this IHasProviderIds instance, string name) + { + ArgumentNullException.ThrowIfNull(instance); + ArgumentException.ThrowIfNullOrEmpty(name); + + instance.ProviderIds?.Remove(name); + } + + /// <summary> + /// Removes a provider id. + /// </summary> + /// <param name="instance">The instance.</param> + /// <param name="provider">The provider.</param> + public static void RemoveProviderId(this IHasProviderIds instance, MetadataProvider provider) + { + ArgumentNullException.ThrowIfNull(instance); + + instance.ProviderIds?.Remove(provider.ToString()); + } } } diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index d026d574fa..1f5163aa8e 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -1,3 +1,5 @@ +using System; + namespace MediaBrowser.Model.Providers { /// <summary> @@ -17,7 +19,9 @@ namespace MediaBrowser.Model.Providers 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,6 +50,7 @@ namespace MediaBrowser.Model.Providers /// <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/Tasks/TaskTriggerInfo.cs b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs index f8a8c727ef..1d8767dc16 100644 --- a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs +++ b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs @@ -13,7 +13,6 @@ namespace MediaBrowser.Model.Tasks public const string TriggerDaily = "DailyTrigger"; public const string TriggerWeekly = "WeeklyTrigger"; public const string TriggerInterval = "IntervalTrigger"; - public const string TriggerSystemEvent = "SystemEventTrigger"; public const string TriggerStartup = "StartupTrigger"; /// <summary> |
