aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs5
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs34
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs71
-rw-r--r--MediaBrowser.Controller/Entities/InternalPeopleQuery.cs10
-rw-r--r--MediaBrowser.Controller/LiveTv/ProgramInfo.cs44
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs199
-rw-r--r--MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs12
7 files changed, 264 insertions, 111 deletions
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 58841e5b78..c25694aba5 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -154,11 +154,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
}
- protected override bool GetBlockUnratedValue(User user)
- {
- return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music);
- }
-
public override UnratedItem GetBlockUnratedType()
{
return UnratedItem.Music;
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 7586b99e77..e312e9d80b 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -22,7 +22,6 @@ using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
@@ -1172,11 +1171,18 @@ namespace MediaBrowser.Controller.Entities
info.Video3DFormat = video.Video3DFormat;
info.Timestamp = video.Timestamp;
- if (video.IsShortcut)
+ if (video.IsShortcut && !string.IsNullOrEmpty(video.ShortcutPath))
{
- info.IsRemote = true;
- info.Path = video.ShortcutPath;
- info.Protocol = MediaSourceManager.GetPathProtocol(info.Path);
+ var shortcutProtocol = MediaSourceManager.GetPathProtocol(video.ShortcutPath);
+
+ // Only allow remote shortcut paths — local file paths in .strm files
+ // could be used to read arbitrary files from the server.
+ if (shortcutProtocol != MediaProtocol.File)
+ {
+ info.IsRemote = true;
+ info.Path = video.ShortcutPath;
+ info.Protocol = shortcutProtocol;
+ }
}
if (string.IsNullOrEmpty(info.Container))
@@ -1601,11 +1607,10 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrEmpty(rating))
{
- Logger.LogDebug("{0} has no parental rating set.", Name);
return !GetBlockUnratedValue(user);
}
- var ratingScore = LocalizationManager.GetRatingScore(rating);
+ var ratingScore = LocalizationManager.GetRatingScore(rating, GetPreferredMetadataCountryCode());
// Could not determine rating level
if (ratingScore is null)
@@ -1647,7 +1652,7 @@ namespace MediaBrowser.Controller.Entities
return null;
}
- return LocalizationManager.GetRatingScore(rating);
+ return LocalizationManager.GetRatingScore(rating, GetPreferredMetadataCountryCode());
}
public List<string> GetInheritedTags()
@@ -2129,17 +2134,6 @@ namespace MediaBrowser.Controller.Entities
};
}
- // Music albums usually don't have dedicated backdrops, so return one from the artist instead
- if (GetType() == typeof(MusicAlbum) && imageType == ImageType.Backdrop)
- {
- var artist = FindParent<MusicArtist>();
-
- if (artist is not null)
- {
- return artist.GetImages(imageType).ElementAtOrDefault(imageIndex);
- }
- }
-
return GetImages(imageType)
.ElementAtOrDefault(imageIndex);
}
@@ -2621,7 +2615,7 @@ namespace MediaBrowser.Controller.Entities
.Select(i => i.OfficialRating)
.Where(i => !string.IsNullOrEmpty(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
- .Select(rating => (rating, LocalizationManager.GetRatingScore(rating)))
+ .Select(rating => (rating, LocalizationManager.GetRatingScore(rating, GetPreferredMetadataCountryCode())))
.OrderBy(i => i.Item2 is null ? 1001 : i.Item2.Score)
.ThenBy(i => i.Item2 is null ? 1001 : i.Item2.SubScore)
.Select(i => i.rating);
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 076a592922..ecbeefbb9d 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -10,6 +10,7 @@ using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Entities
{
@@ -388,5 +389,75 @@ namespace MediaBrowser.Controller.Entities
User = user;
}
+
+ public void ApplyFilters(ItemFilter[] filters)
+ {
+ static void ThrowConflictingFilters()
+ => throw new ArgumentException("Conflicting filters", nameof(filters));
+
+ foreach (var filter in filters)
+ {
+ switch (filter)
+ {
+ case ItemFilter.IsFolder:
+ if (filters.Contains(ItemFilter.IsNotFolder))
+ {
+ ThrowConflictingFilters();
+ }
+
+ IsFolder = true;
+ break;
+ case ItemFilter.IsNotFolder:
+ if (filters.Contains(ItemFilter.IsFolder))
+ {
+ ThrowConflictingFilters();
+ }
+
+ IsFolder = false;
+ break;
+ case ItemFilter.IsUnplayed:
+ if (filters.Contains(ItemFilter.IsPlayed))
+ {
+ ThrowConflictingFilters();
+ }
+
+ IsPlayed = false;
+ break;
+ case ItemFilter.IsPlayed:
+ if (filters.Contains(ItemFilter.IsUnplayed))
+ {
+ ThrowConflictingFilters();
+ }
+
+ IsPlayed = true;
+ break;
+ case ItemFilter.IsFavorite:
+ IsFavorite = true;
+ break;
+ case ItemFilter.IsResumable:
+ IsResumable = true;
+ break;
+ case ItemFilter.Likes:
+ if (filters.Contains(ItemFilter.Dislikes))
+ {
+ ThrowConflictingFilters();
+ }
+
+ IsLiked = true;
+ break;
+ case ItemFilter.Dislikes:
+ if (filters.Contains(ItemFilter.Likes))
+ {
+ ThrowConflictingFilters();
+ }
+
+ IsLiked = false;
+ break;
+ case ItemFilter.IsFavoriteOrLikes:
+ IsFavoriteOrLiked = true;
+ break;
+ }
+ }
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
index 203a16a668..e12ba22343 100644
--- a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
@@ -21,6 +21,8 @@ namespace MediaBrowser.Controller.Entities
ExcludePersonTypes = excludePersonTypes;
}
+ public int? StartIndex { get; set; }
+
/// <summary>
/// Gets or sets the maximum number of items the query should return.
/// </summary>
@@ -28,6 +30,8 @@ namespace MediaBrowser.Controller.Entities
public Guid ItemId { get; set; }
+ public Guid? ParentId { get; set; }
+
public IReadOnlyList<string> PersonTypes { get; }
public IReadOnlyList<string> ExcludePersonTypes { get; }
@@ -38,6 +42,12 @@ namespace MediaBrowser.Controller.Entities
public string NameContains { get; set; }
+ public string NameStartsWith { get; set; }
+
+ public string NameLessThan { get; set; }
+
+ public string NameStartsWithOrGreater { get; set; }
+
public User User { get; set; }
public bool? IsFavorite { get; set; }
diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
index 3c3ac2471f..905aad17b9 100644
--- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -12,45 +10,45 @@ namespace MediaBrowser.Controller.LiveTv
{
public ProgramInfo()
{
- Genres = new List<string>();
+ Genres = [];
- ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ ProviderIds = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
+ SeriesProviderIds = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Gets or sets the id of the program.
/// </summary>
- public string Id { get; set; }
+ public string? Id { get; set; }
/// <summary>
/// Gets or sets the channel identifier.
/// </summary>
/// <value>The channel identifier.</value>
- public string ChannelId { get; set; }
+ public string? ChannelId { get; set; }
/// <summary>
/// Gets or sets the name of the program.
/// </summary>
- public string Name { get; set; }
+ public string? Name { get; set; }
/// <summary>
/// Gets or sets the official rating.
/// </summary>
/// <value>The official rating.</value>
- public string OfficialRating { get; set; }
+ public string? OfficialRating { get; set; }
/// <summary>
/// Gets or sets the overview.
/// </summary>
/// <value>The overview.</value>
- public string Overview { get; set; }
+ public string? Overview { get; set; }
/// <summary>
/// Gets or sets the short overview.
/// </summary>
/// <value>The short overview.</value>
- public string ShortOverview { get; set; }
+ public string? ShortOverview { get; set; }
/// <summary>
/// Gets or sets the start date of the program, in UTC.
@@ -108,25 +106,25 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets or sets the episode title.
/// </summary>
/// <value>The episode title.</value>
- public string EpisodeTitle { get; set; }
+ public string? EpisodeTitle { get; set; }
/// <summary>
/// Gets or sets the image path if it can be accessed directly from the file system.
/// </summary>
/// <value>The image path.</value>
- public string ImagePath { get; set; }
+ public string? ImagePath { get; set; }
/// <summary>
/// Gets or sets the image url if it can be downloaded.
/// </summary>
/// <value>The image URL.</value>
- public string ImageUrl { get; set; }
+ public string? ImageUrl { get; set; }
- public string ThumbImageUrl { get; set; }
+ public string? ThumbImageUrl { get; set; }
- public string LogoImageUrl { get; set; }
+ public string? LogoImageUrl { get; set; }
- public string BackdropImageUrl { get; set; }
+ public string? BackdropImageUrl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance has image.
@@ -188,19 +186,19 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets or sets the home page URL.
/// </summary>
/// <value>The home page URL.</value>
- public string HomePageUrl { get; set; }
+ public string? HomePageUrl { get; set; }
/// <summary>
/// Gets or sets the series identifier.
/// </summary>
/// <value>The series identifier.</value>
- public string SeriesId { get; set; }
+ public string? SeriesId { get; set; }
/// <summary>
/// Gets or sets the show identifier.
/// </summary>
/// <value>The show identifier.</value>
- public string ShowId { get; set; }
+ public string? ShowId { get; set; }
/// <summary>
/// Gets or sets the season number.
@@ -218,10 +216,10 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets or sets the etag.
/// </summary>
/// <value>The etag.</value>
- public string Etag { get; set; }
+ public string? Etag { get; set; }
- public Dictionary<string, string> ProviderIds { get; set; }
+ public Dictionary<string, string?> ProviderIds { get; set; }
- public Dictionary<string, string> SeriesProviderIds { get; set; }
+ public Dictionary<string, string?> SeriesProviderIds { get; set; }
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 11eee1a372..117f376724 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -33,18 +33,18 @@ namespace MediaBrowser.Controller.MediaEncoding
public partial class EncodingHelper
{
/// <summary>
- /// The codec validation regex.
+ /// The codec validation regex string.
/// This regular expression matches strings that consist of alphanumeric characters, hyphens,
/// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
/// This should matches all common valid codecs.
/// </summary>
- public const string ContainerValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
+ public const string ContainerValidationRegexStr = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
/// <summary>
- /// The level validation regex.
+ /// The level validation regex string.
/// This regular expression matches strings representing a double.
/// </summary>
- public const string LevelValidationRegex = @"-?[0-9]+(?:\.[0-9]+)?";
+ public const string LevelValidationRegexStr = @"-?[0-9]+(?:\.[0-9]+)?";
private const string _defaultMjpegEncoder = "mjpeg";
@@ -85,8 +85,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
-
- private static readonly Regex _containerValidationRegex = new(ContainerValidationRegex, RegexOptions.Compiled);
+ private readonly Version _minFFmpegReadrateCatchupOption = new Version(8, 0);
private static readonly string[] _videoProfilesH264 =
[
@@ -180,6 +179,22 @@ namespace MediaBrowser.Controller.MediaEncoding
RemoveHdr10Plus,
}
+ /// <summary>
+ /// The codec validation regex.
+ /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
+ /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
+ /// This should matches all common valid codecs.
+ /// </summary>
+ [GeneratedRegex(ContainerValidationRegexStr)]
+ public static partial Regex ContainerValidationRegex();
+
+ /// <summary>
+ /// The level validation regex string.
+ /// This regular expression matches strings representing a double.
+ /// </summary>
+ [GeneratedRegex(LevelValidationRegexStr)]
+ public static partial Regex LevelValidationRegex();
+
[GeneratedRegex(@"\s+")]
private static partial Regex WhiteSpaceRegex();
@@ -405,7 +420,9 @@ namespace MediaBrowser.Controller.MediaEncoding
}
return state.VideoStream.VideoRange == VideoRange.HDR
- && IsDoviWithHdr10Bl(state.VideoStream);
+ && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
+ || IsHdr10Plus(state.VideoStream)
+ || IsDoviWithHdr10Bl(state.VideoStream));
}
private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
@@ -420,8 +437,10 @@ namespace MediaBrowser.Controller.MediaEncoding
// Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce correct mapping results with transcoding.
// All other HDR formats working.
return state.VideoStream.VideoRange == VideoRange.HDR
- && (IsDoviWithHdr10Bl(state.VideoStream)
- || state.VideoStream.VideoRangeType is VideoRangeType.HLG);
+ && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
+ || IsHdr10Plus(state.VideoStream)
+ || IsDoviWithHdr10Bl(state.VideoStream)
+ || state.VideoStream.VideoRangeType == VideoRangeType.HLG);
}
private bool IsVideoStreamHevcRext(EncodingJobInfo state)
@@ -476,7 +495,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetMjpegEncoder(state, encodingOptions);
}
- if (_containerValidationRegex.IsMatch(codec))
+ if (ContainerValidationRegex().IsMatch(codec))
{
return codec.ToLowerInvariant();
}
@@ -517,7 +536,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public static string GetInputFormat(string container)
{
- if (string.IsNullOrEmpty(container) || !_containerValidationRegex.IsMatch(container))
+ if (string.IsNullOrEmpty(container) || !ContainerValidationRegex().IsMatch(container))
{
return null;
}
@@ -735,7 +754,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var codec = state.OutputAudioCodec;
- if (!_containerValidationRegex.IsMatch(codec))
+ if (!ContainerValidationRegex().IsMatch(codec))
{
codec = "aac";
}
@@ -1267,6 +1286,20 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ // Use analyzeduration also for subtitle streams to improve resolution detection with streams inside MKS files
+ var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
+ if (!string.IsNullOrEmpty(analyzeDurationArgument))
+ {
+ arg.Append(' ').Append(analyzeDurationArgument);
+ }
+
+ // Apply probesize, too, if configured
+ var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
+ if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
+ {
+ arg.Append(' ').Append(ffmpegProbeSizeArgument);
+ }
+
// Also seek the external subtitles stream.
var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
if (!string.IsNullOrEmpty(seekSubParam))
@@ -1552,14 +1585,15 @@ namespace MediaBrowser.Controller.MediaEncoding
int bitrate = state.OutputVideoBitrate.Value;
- // Bit rate under 1000k is not allowed in h264_qsv
+ // Bit rate under 1000k is not allowed in h264_qsv.
if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
bitrate = Math.Max(bitrate, 1000);
}
- // Currently use the same buffer size for all encoders
- int bufsize = bitrate * 2;
+ // Currently use the same buffer size for all non-QSV encoders.
+ // Use long arithmetic to prevent int32 overflow for very high bitrate values.
+ int bufsize = (int)Math.Min((long)bitrate * 2, int.MaxValue);
if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
{
@@ -1589,7 +1623,13 @@ namespace MediaBrowser.Controller.MediaEncoding
// Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
// Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene changes
- return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {bitrate + 1} -rc_init_occupancy {bitrate * 2} -bufsize {bitrate * 4}");
+ // Use long arithmetic and clamp to int.MaxValue to prevent int32 overflow
+ // (e.g. bitrate * 4 wraps to a negative value for bitrates above ~537 million)
+ int qsvMaxrate = (int)Math.Min((long)bitrate + 1, int.MaxValue);
+ int qsvInitOcc = (int)Math.Min((long)bitrate * 2, int.MaxValue);
+ int qsvBufsize = (int)Math.Min((long)bitrate * 4, int.MaxValue);
+
+ return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {qsvMaxrate} -rc_init_occupancy {qsvInitOcc} -bufsize {qsvBufsize}");
}
if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
@@ -1768,38 +1808,40 @@ namespace MediaBrowser.Controller.MediaEncoding
public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
{
- if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
+ if (!double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
+ {
+ return null;
+ }
+
+ if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
+ // Transcode to level 5.3 (15) and lower for maximum compatibility.
+ // https://en.wikipedia.org/wiki/AV1#Levels
+ if (requestLevel < 0 || requestLevel >= 15)
{
- // Transcode to level 5.3 (15) and lower for maximum compatibility.
- // https://en.wikipedia.org/wiki/AV1#Levels
- if (requestLevel < 0 || requestLevel >= 15)
- {
- return "15";
- }
+ return "15";
}
- else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
+ }
+ else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
+ {
+ // Transcode to level 5.0 and lower for maximum compatibility.
+ // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
+ // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
+ // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
+ if (requestLevel < 0 || requestLevel >= 150)
{
- // Transcode to level 5.0 and lower for maximum compatibility.
- // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
- // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
- // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
- if (requestLevel < 0 || requestLevel >= 150)
- {
- return "150";
- }
+ return "150";
}
- else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+ }
+ else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ // Transcode to level 5.1 and lower for maximum compatibility.
+ // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
+ // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
+ if (requestLevel < 0 || requestLevel >= 51)
{
- // Transcode to level 5.1 and lower for maximum compatibility.
- // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
- // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
- if (requestLevel < 0 || requestLevel >= 51)
- {
- return "51";
- }
+ return "51";
}
}
@@ -2189,12 +2231,10 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- var level = state.GetRequestedLevel(targetVideoCodec);
+ var level = NormalizeTranscodingLevel(state, state.GetRequestedLevel(targetVideoCodec));
if (!string.IsNullOrEmpty(level))
{
- level = NormalizeTranscodingLevel(state, level);
-
// libx264, QSV, AMF can adjust the given level to match the output.
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
@@ -2592,8 +2632,16 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
- return Math.Min(bitrate ?? 0, int.MaxValue / 2);
+ // Cap the max target bitrate to 400 Mbps.
+ // No consumer or professional hardware transcode target exceeds this value
+ // (Intel QSV tops out at ~300 Mbps for H.264; HEVC High Tier Level 5.x is ~240 Mbps).
+ // Without this cap, plugin-provided MPEG-TS streams with no usable bitrate metadata
+ // can produce unreasonably large -bufsize/-maxrate values for the encoder.
+ // Note: the existing FallbackMaxStreamingBitrate mechanism (default 30 Mbps) only
+ // applies when a LiveStreamId is set (M3U/HDHR sources). Plugin streams and other
+ // sources that bypass the LiveTV pipeline are not covered by it.
+ const int MaxSaneBitrate = 400_000_000; // 400 Mbps
+ return Math.Min(bitrate ?? 0, MaxSaneBitrate);
}
private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
@@ -6359,17 +6407,15 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Block unsupported H.264 Hi422P and Hi444PP profiles, which can be encoded with 4:2:0 pixel format
- if (string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
+ && ((videoStream.Profile?.Contains("4:2:2", StringComparison.OrdinalIgnoreCase) ?? false)
+ || (videoStream.Profile?.Contains("4:4:4", StringComparison.OrdinalIgnoreCase) ?? false)))
{
- if (videoStream.Profile.Contains("4:2:2", StringComparison.OrdinalIgnoreCase)
- || videoStream.Profile.Contains("4:4:4", StringComparison.OrdinalIgnoreCase))
+ // VideoToolbox on Apple Silicon has H.264 Hi444PP and theoretically also has Hi422P
+ if (!(hardwareAccelerationType == HardwareAccelerationType.videotoolbox
+ && RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64)))
{
- // VideoToolbox on Apple Silicon has H.264 Hi444PP and theoretically also has Hi422P
- if (!(hardwareAccelerationType == HardwareAccelerationType.videotoolbox
- && RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64)))
- {
- return null;
- }
+ return null;
}
}
@@ -7123,9 +7169,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
+ private string GetFfmpegAnalyzeDurationArg(EncodingJobInfo state)
{
- var inputModifier = string.Empty;
var analyzeDurationArgument = string.Empty;
// Apply -analyzeduration as per the environment variable,
@@ -7141,6 +7186,26 @@ namespace MediaBrowser.Controller.MediaEncoding
analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
}
+ return analyzeDurationArgument;
+ }
+
+ private string GetFfmpegProbesizeArg()
+ {
+ var ffmpegProbeSize = _config.GetFFmpegProbeSize();
+
+ if (!string.IsNullOrEmpty(ffmpegProbeSize))
+ {
+ return $"-probesize {ffmpegProbeSize}";
+ }
+
+ return string.Empty;
+ }
+
+ public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
+ {
+ var inputModifier = string.Empty;
+ var analyzeDurationArgument = GetFfmpegAnalyzeDurationArg(state);
+
if (!string.IsNullOrEmpty(analyzeDurationArgument))
{
inputModifier += " " + analyzeDurationArgument;
@@ -7149,11 +7214,11 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier = inputModifier.Trim();
// Apply -probesize if configured
- var ffmpegProbeSize = _config.GetFFmpegProbeSize();
+ var ffmpegProbeSizeArgument = GetFfmpegProbesizeArg();
- if (!string.IsNullOrEmpty(ffmpegProbeSize))
+ if (!string.IsNullOrEmpty(ffmpegProbeSizeArgument))
{
- inputModifier += $" -probesize {ffmpegProbeSize}";
+ inputModifier += " " + ffmpegProbeSizeArgument;
}
var userAgentParam = GetUserAgentParam(state);
@@ -7193,8 +7258,10 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
}
+ int readrate = 0;
if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
{
+ readrate = 1;
inputModifier += " -re";
}
else if (encodingOptions.EnableSegmentDeletion
@@ -7205,7 +7272,15 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// Set an input read rate limit 10x for using SegmentDeletion with stream-copy
// to prevent ffmpeg from exiting prematurely (due to fast drive)
- inputModifier += " -readrate 10";
+ readrate = 10;
+ inputModifier += $" -readrate {readrate}";
+ }
+
+ // Set a larger catchup value to revert to the old behavior,
+ // otherwise, remuxing might stall due to this new option
+ if (readrate > 0 && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateCatchupOption)
+ {
+ inputModifier += $" -readrate_catchup {readrate * 100}";
}
var flags = new List<string>();
diff --git a/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs b/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs
index 5a6d15d781..54da218530 100644
--- a/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs
+++ b/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
@@ -31,4 +32,13 @@ public interface IMediaSegmentProvider
/// <param name="item">The base item to extract segments from.</param>
/// <returns>True if item is supported, otherwise false.</returns>
ValueTask<bool> Supports(BaseItem item);
+
+ /// <summary>
+ /// Called when extracted segment data for an item is being pruned.
+ /// Providers should delete any cached analysis data they hold for the given item.
+ /// </summary>
+ /// <param name="itemId">The item whose data is being pruned.</param>
+ /// <param name="cancellationToken">Abort token.</param>
+ /// <returns>A task representing the asynchronous cleanup operation.</returns>
+ Task CleanupExtractedData(Guid itemId, CancellationToken cancellationToken);
}