aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Model')
-rw-r--r--MediaBrowser.Model/Configuration/TrickplayOptions.cs6
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs88
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs36
-rw-r--r--MediaBrowser.Model/Entities/ProviderIdsExtensions.cs60
-rw-r--r--MediaBrowser.Model/Providers/ExternalIdInfo.cs5
-rw-r--r--MediaBrowser.Model/Tasks/TaskTriggerInfo.cs1
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>