Merge branch 'master' into providermanager-cleanup

This commit is contained in:
Claus Vium
2022-10-07 09:57:16 +02:00
committed by GitHub
470 changed files with 6148 additions and 9078 deletions

View File

@@ -50,6 +50,14 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>BlurHash.</returns>
string GetImageBlurHash(string path);
/// <summary>
/// Gets the blurhash of the image.
/// </summary>
/// <param name="path">Path to the image file.</param>
/// <param name="imageDimensions">The image dimensions.</param>
/// <returns>BlurHash.</returns>
string GetImageBlurHash(string path, ImageDimensions imageDimensions);
/// <summary>
/// Gets the image cache tag.
/// </summary>

View File

@@ -171,10 +171,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException">Throws if child is null.</exception>
public void AddVirtualChild(BaseItem child)
{
if (child == null)
{
throw new ArgumentNullException(nameof(child));
}
ArgumentNullException.ThrowIfNull(child);
_virtualChildren.Add(child);
}

View File

@@ -169,8 +169,8 @@ namespace MediaBrowser.Controller.Entities.Audio
var childUpdateType = ItemUpdateType.None;
// Refresh songs
foreach (var item in items)
// Refresh songs only and not m3u files in album folder
foreach (var item in items.OfType<Audio>())
{
cancellationToken.ThrowIfCancellationRequested();

View File

@@ -8,9 +8,9 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;

View File

@@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.Audio

View File

@@ -11,7 +11,6 @@ using System.Text;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -1102,10 +1101,7 @@ namespace MediaBrowser.Controller.Entities
private MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, BaseItem item, MediaSourceType type)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
ArgumentNullException.ThrowIfNull(item);
var protocol = item.PathProtocol;
@@ -1545,10 +1541,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException">If user is null.</exception>
public bool IsParentalAllowed(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
ArgumentNullException.ThrowIfNull(user);
if (!IsVisibleViaTags(user))
{
@@ -1668,10 +1661,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException"><paramref name="user" /> is <c>null</c>.</exception>
public virtual bool IsVisible(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
ArgumentNullException.ThrowIfNull(user);
return IsParentalAllowed(user);
}
@@ -1843,10 +1833,7 @@ namespace MediaBrowser.Controller.Entities
DateTime? datePlayed,
bool resetPosition)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
ArgumentNullException.ThrowIfNull(user);
var data = UserDataManager.GetUserData(user, this);
@@ -1877,10 +1864,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException">Throws if user is null.</exception>
public virtual void MarkUnplayed(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
ArgumentNullException.ThrowIfNull(user);
var data = UserDataManager.GetUserData(user, this);
@@ -2111,10 +2095,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Image index.</returns>
public int GetImageIndex(ItemImageInfo image)
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
ArgumentNullException.ThrowIfNull(image);
if (image.Type == ImageType.Chapter)
{
@@ -2321,10 +2302,7 @@ namespace MediaBrowser.Controller.Entities
public virtual bool IsUnplayed(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
ArgumentNullException.ThrowIfNull(user);
var userdata = UserDataManager.GetUserData(user, this);
@@ -2617,7 +2595,8 @@ namespace MediaBrowser.Controller.Entities
return ExtraIds
.Select(LibraryManager.GetItemById)
.Where(i => i != null)
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value))
.OrderBy(i => i.SortName);
}
public virtual long GetRunTimeTicksForPlayState()

View File

@@ -71,15 +71,9 @@ namespace MediaBrowser.Controller.Entities
where T : BaseItem
where TU : BaseItem
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
ArgumentNullException.ThrowIfNull(source);
if (dest == null)
{
throw new ArgumentNullException(nameof(dest));
}
ArgumentNullException.ThrowIfNull(dest);
var destProps = typeof(TU).GetProperties().Where(x => x.CanWrite).ToList();

View File

@@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System.Text.Json.Serialization;
@@ -13,7 +11,7 @@ namespace MediaBrowser.Controller.Entities
public abstract class BasePluginFolder : Folder, ICollectionFolder
{
[JsonIgnore]
public virtual string CollectionType => null;
public virtual string? CollectionType => null;
[JsonIgnore]
public override bool SupportsInheritedParentImages => false;

View File

@@ -1,5 +1,3 @@
#nullable disable
using System;
using System.Linq;
using Jellyfin.Extensions;
@@ -19,9 +17,11 @@ namespace MediaBrowser.Controller.Entities
/// <param name="url">Trailer URL.</param>
public static void AddTrailerUrl(this BaseItem item, string url)
{
if (string.IsNullOrEmpty(url))
ArgumentNullException.ThrowIfNull(url);
if (url.Length == 0)
{
throw new ArgumentNullException(nameof(url));
throw new ArgumentException("String can't be empty", nameof(url));
}
var current = item.RemoteTrailers.FirstOrDefault(i => string.Equals(i.Url, url, StringComparison.OrdinalIgnoreCase));

View File

@@ -860,7 +860,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
if (!string.IsNullOrEmpty(query.AdjacentTo))
if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
{
Logger.LogDebug("Query requires post-filtering due to AdjacentTo");
return true;
@@ -892,29 +892,7 @@ namespace MediaBrowser.Controller.Entities
private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items)
{
var ids = query.ItemIds;
int size = items.Count;
// ids can potentially contain non-unique guids, but query result cannot,
// so we include only first occurrence of each guid
var positions = new Dictionary<Guid, int>(size);
int index = 0;
for (int i = 0; i < ids.Length; i++)
{
if (positions.TryAdd(ids[i], index))
{
index++;
}
}
var newItems = new BaseItem[size];
for (int i = 0; i < size; i++)
{
var item = items[i];
newItems[positions[item.Id]] = item;
}
return newItems;
return items.OrderBy(i => Array.IndexOf(query.ItemIds, i.Id)).ToArray();
}
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
@@ -1029,9 +1007,9 @@ namespace MediaBrowser.Controller.Entities
#pragma warning restore CA1309
// This must be the last filter
if (!string.IsNullOrEmpty(query.AdjacentTo))
if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
{
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo);
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
return UserViewBuilder.SortAndPage(items, null, query, LibraryManager, enableSorting);
@@ -1045,10 +1023,7 @@ namespace MediaBrowser.Controller.Entities
IServerConfigurationManager configurationManager,
ICollectionManager collectionManager)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
ArgumentNullException.ThrowIfNull(items);
if (CollapseBoxSetItems(query, queryParent, user, configurationManager))
{
@@ -1295,20 +1270,14 @@ namespace MediaBrowser.Controller.Entities
public List<BaseItem> GetChildren(User user, bool includeLinkedChildren)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
ArgumentNullException.ThrowIfNull(user);
return GetChildren(user, includeLinkedChildren, null);
}
public virtual List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
ArgumentNullException.ThrowIfNull(user);
// the true root should return our users root folder children
if (IsPhysicalRoot)
@@ -1389,10 +1358,7 @@ namespace MediaBrowser.Controller.Entities
public virtual IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
ArgumentNullException.ThrowIfNull(user);
var result = new Dictionary<Guid, BaseItem>();

View File

@@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities

View File

@@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Entities
public Guid[] ExcludeItemIds { get; set; }
public string? AdjacentTo { get; set; }
public Guid? AdjacentTo { get; set; }
public string[] PersonTypes { get; set; }

View File

@@ -11,10 +11,7 @@ namespace MediaBrowser.Controller.Entities
{
public static void AddPerson(List<PersonInfo> people, PersonInfo person)
{
if (person == null)
{
throw new ArgumentNullException(nameof(person));
}
ArgumentNullException.ThrowIfNull(person);
if (string.IsNullOrEmpty(person.Name))
{

View File

@@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using Microsoft.Extensions.Logging;

View File

@@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities

View File

@@ -244,7 +244,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary>
/// <param name="replaceAllMetadata"><c>true</c> to replace metdata, <c>false</c> to not.</param>
/// <param name="replaceAllMetadata"><c>true</c> to replace metadata, <c>false</c> to not.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{

View File

@@ -184,6 +184,11 @@ namespace MediaBrowser.Controller.Entities.TV
list.Insert(0, key);
}
if (this.TryGetProviderId(MetadataProvider.Custom, out key))
{
list.Insert(0, key);
}
return list;
}
@@ -261,7 +266,7 @@ namespace MediaBrowser.Controller.Entities.TV
DtoOptions = options
};
if (!user.DisplayMissingEpisodes)
if (user == null || !user.DisplayMissingEpisodes)
{
query.IsMissing = false;
}

View File

@@ -433,9 +433,9 @@ namespace MediaBrowser.Controller.Entities
var user = query.User;
// This must be the last filter
if (!string.IsNullOrEmpty(query.AdjacentTo))
if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
{
items = FilterForAdjacency(items.ToList(), query.AdjacentTo);
items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
return SortAndPage(items, totalRecordLimit, query, libraryManager, true);
@@ -985,10 +985,9 @@ namespace MediaBrowser.Controller.Entities
return _userViewManager.GetUserSubView(parent.Id, type, localizationKey, sortName);
}
public static IEnumerable<BaseItem> FilterForAdjacency(List<BaseItem> list, string adjacentToId)
public static IEnumerable<BaseItem> FilterForAdjacency(List<BaseItem> list, Guid adjacentTo)
{
var adjacentToIdGuid = new Guid(adjacentToId);
var adjacentToItem = list.FirstOrDefault(i => i.Id.Equals(adjacentToIdGuid));
var adjacentToItem = list.FirstOrDefault(i => i.Id.Equals(adjacentTo));
var index = list.IndexOf(adjacentToItem);
@@ -1005,7 +1004,7 @@ namespace MediaBrowser.Controller.Entities
nextId = list[index + 1].Id;
}
return list.Where(i => i.Id.Equals(previousId) || i.Id.Equals(nextId) || i.Id.Equals(adjacentToIdGuid));
return list.Where(i => i.Id.Equals(previousId) || i.Id.Equals(nextId) || i.Id.Equals(adjacentTo));
}
}
}

View File

@@ -1,5 +1,3 @@
#nullable disable
using System;
using System.Collections.Generic;
using Jellyfin.Data.Entities;
@@ -50,7 +48,7 @@ namespace MediaBrowser.Controller
/// <param name="itemId">The item id.</param>
/// <param name="client">The client string.</param>
/// <returns>The dictionary of custom item display preferences.</returns>
Dictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client);
Dictionary<string, string?> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client);
/// <summary>
/// Sets the custom item display preference for the user and client.
@@ -59,7 +57,7 @@ namespace MediaBrowser.Controller
/// <param name="itemId">The item id.</param>
/// <param name="client">The client id.</param>
/// <param name="customPreferences">A dictionary of custom item display preferences.</param>
void SetCustomItemDisplayPreferences(Guid userId, Guid itemId, string client, Dictionary<string, string> customPreferences);
void SetCustomItemDisplayPreferences(Guid userId, Guid itemId, string client, Dictionary<string, string?> customPreferences);
/// <summary>
/// Saves changes made to the database.

View File

@@ -40,10 +40,7 @@ namespace MediaBrowser.Controller.IO
throw new ArgumentNullException(nameof(path));
}
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
ArgumentNullException.ThrowIfNull(args);
var entries = directoryService.GetFileSystemEntries(path);

View File

@@ -4,6 +4,7 @@
using System.Net;
using MediaBrowser.Common;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.System;
using Microsoft.AspNetCore.Http;
@@ -74,9 +75,10 @@ namespace MediaBrowser.Controller
/// <summary>
/// Gets an URL that can be used to access the API over LAN.
/// </summary>
/// <param name="hostname">An optional hostname to use.</param>
/// <param name="allowHttps">A value indicating whether to allow HTTPS.</param>
/// <returns>The API URL.</returns>
string GetApiUrlForLocalAccess(bool allowHttps = true);
string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true);
/// <summary>
/// Gets a local (LAN) URL that can be used to access the API.

View File

@@ -570,5 +570,13 @@ namespace MediaBrowser.Controller.Library
Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason);
BaseItem GetParentItem(Guid? parentId, Guid? userId);
/// <summary>
/// Queue a library scan.
/// </summary>
/// <remarks>
/// This exists so plugins can trigger a library scan.
/// </remarks>
void QueueLibraryScan();
}
}

View File

@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Diacritics.Extensions;
using Jellyfin.Extensions;
namespace MediaBrowser.Controller.Library
{

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.8.0</VersionPrefix>
<VersionPrefix>10.9.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -18,7 +18,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Diacritics" Version="3.3.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
@@ -57,7 +56,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>

View File

@@ -75,6 +75,12 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <value>The profile.</value>
public string Profile { get; set; }
/// <summary>
/// Gets or sets the video range type.
/// </summary>
/// <value>The video range type.</value>
public string VideoRangeType { get; set; }
/// <summary>
/// Gets or sets the level.
/// </summary>

View File

@@ -13,11 +13,13 @@ using System.Threading;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -32,6 +34,8 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly IApplicationPaths _appPaths;
private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config;
private readonly Version _minKernelVersioni915Hang = new Version(5, 18);
private static readonly string[] _videoProfilesH264 = new[]
{
@@ -54,11 +58,13 @@ namespace MediaBrowser.Controller.MediaEncoding
public EncodingHelper(
IApplicationPaths appPaths,
IMediaEncoder mediaEncoder,
ISubtitleEncoder subtitleEncoder)
ISubtitleEncoder subtitleEncoder,
IConfiguration config)
{
_appPaths = appPaths;
_mediaEncoder = mediaEncoder;
_subtitleEncoder = subtitleEncoder;
_config = config;
}
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -120,6 +126,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& _mediaEncoder.SupportsFilter("scale_vaapi")
&& _mediaEncoder.SupportsFilter("deinterlace_vaapi")
&& _mediaEncoder.SupportsFilter("tonemap_vaapi")
&& _mediaEncoder.SupportsFilter("procamp_vaapi")
&& _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
&& _mediaEncoder.SupportsFilter("hwupload_vaapi");
}
@@ -144,35 +151,50 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
if (state.VideoStream == null)
if (state.VideoStream == null
|| !options.EnableTonemapping
|| GetVideoColorBitDepth(state) != 10)
{
return false;
}
return options.EnableTonemapping
&& (string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.VideoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
&& GetVideoColorBitDepth(state) == 10;
if (string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
&& string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
&& string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase))
{
// Only native SW decoder and HW accelerator can parse dovi rpu.
var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder;
}
return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
&& (string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.VideoStream.VideoRangeType, "HLG", StringComparison.OrdinalIgnoreCase));
}
private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
if (state.VideoStream == null)
if (state.VideoStream == null
|| !options.EnableVppTonemapping
|| GetVideoColorBitDepth(state) != 10)
{
return false;
}
// Native VPP tonemapping may come to QSV in the future.
return options.EnableVppTonemapping
&& string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
&& GetVideoColorBitDepth(state) == 10;
return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
&& string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Gets the name of the output video codec.
/// </summary>
/// <param name="state">Encording state.</param>
/// <param name="state">Encoding state.</param>
/// <param name="encodingOptions">Encoding options.</param>
/// <returns>Encoder string.</returns>
public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -234,6 +256,21 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty;
}
/// <summary>
/// Gets the referer param.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
public string GetRefererParam(EncodingJobInfo state)
{
if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
{
return "-referer \"" + referer + "\"";
}
return string.Empty;
}
public static string GetInputFormat(string container)
{
if (string.IsNullOrEmpty(container))
@@ -516,8 +553,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
{
// flac is experimental in mp4 muxer
return "flac -strict -2";
return "flac";
}
return codec.ToLowerInvariant();
@@ -696,6 +732,9 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (_mediaEncoder.IsVaapiDeviceInteli965)
{
// Only override i965 since it has lower priority than iHD in libva lookup.
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
args.Append(GetVaapiDeviceArgs(null, "i965", null, VaapiAlias));
}
else
@@ -907,6 +946,13 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
}
// Disable auto inserted SW scaler for HW decoders in case of changed resolution.
var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
if (!isSwDecoder)
{
arg.Append(" -autoscale 0");
}
return arg.ToString();
}
@@ -1024,7 +1070,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
{
return FormattableString.Invariant($" -qmin 18 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
// Override the too high default qmin 18 in transcoding preset
return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
@@ -1062,10 +1109,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
// Clients may direct play higher than level 41, but there's no reason to transcode higher.
if (requestLevel >= 41)
// 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 >= 51)
{
return "41";
return "51";
}
}
}
@@ -1118,16 +1167,15 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.SubtitleStream.IsExternal)
{
var subtitlePath = state.SubtitleStream.Path;
var charsetParam = string.Empty;
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
{
var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
subtitlePath,
state.SubtitleStream.Language,
state.MediaSource.Protocol,
CancellationToken.None).GetAwaiter().GetResult();
state.SubtitleStream,
state.SubtitleStream.Language,
state.MediaSource,
CancellationToken.None).GetAwaiter().GetResult();
if (!string.IsNullOrEmpty(charenc))
{
@@ -1139,7 +1187,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"subtitles=f='{0}'{1}{2}{3}{4}{5}",
_mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
_mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
charsetParam,
alphaParam,
sub2videoParam,
@@ -1220,10 +1268,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// Example: we encoded half of desired length, then codec detected
// scene cut and inserted a keyframe; next forced keyframe would
// be created outside of segment, which breaks seeking.
// -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe.
gopArg = string.Format(
CultureInfo.InvariantCulture,
" -g:v:0 {0} -keyint_min:v:0 {0} -sc_threshold:v:0 0",
" -g:v:0 {0} -keyint_min:v:0 {0}",
Math.Ceiling(segmentLength * framerate.Value));
}
@@ -1243,6 +1290,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
{
args += keyFrameArg;
// prevent the libx264 from post processing to break the set keyframe.
if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
{
args += " -sc_threshold:v:0 0";
}
}
else
{
@@ -1271,6 +1324,10 @@ namespace MediaBrowser.Controller.MediaEncoding
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
var intelLowPowerHwEncoding = false;
// Workaround for linux 5.18+ i915 hang at cost of performance.
// https://github.com/intel/media-driver/issues/1456
var enableWaFori915Hang = false;
if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
@@ -1286,6 +1343,20 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
if (OperatingSystem.IsLinux() && Environment.OSVersion.Version >= _minKernelVersioni915Hang)
{
var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
|| vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
&& IsVaapiSupported(state)
&& IsOpenclFullSupported()
&& !IsVaapiVppTonemapAvailable(state, encodingOptions)
&& IsHwTonemapAvailable(state, encodingOptions);
enableWaFori915Hang = isIntelDecoder && doOclTonemap;
}
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
@@ -1294,6 +1365,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
}
else
{
enableWaFori915Hang = false;
}
}
if (intelLowPowerHwEncoding)
@@ -1301,6 +1376,11 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -low_power 1";
}
if (enableWaFori915Hang)
{
param += " -async_depth 1";
}
var isVc1 = string.Equals(state.VideoStream?.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
@@ -1682,6 +1762,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Can't stream copy if we're burning in subtitles
if (request.SubtitleStreamIndex.HasValue
&& request.SubtitleStreamIndex.Value >= 0
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
{
return false;
@@ -1728,6 +1809,20 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
if (requestedRangeTypes.Length > 0)
{
if (string.IsNullOrEmpty(videoStream.VideoRangeType))
{
return false;
}
if (!requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
// Video width must fall within requested value
if (request.MaxWidth.HasValue
&& (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
@@ -1868,7 +1963,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return request.EnableAutoStreamCopy;
}
public int? GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
{
var bitrate = request.VideoBitRate;
@@ -1900,7 +1995,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
return bitrate;
// Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
return Math.Min(bitrate ?? 0, int.MaxValue / 2);
}
private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
@@ -1980,6 +2076,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
{
@@ -2213,13 +2311,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return state.IsInputVideo ? "-sn" : string.Empty;
}
// We have media info, but we don't know the stream indexes
// We have media info, but we don't know the stream index
if (state.VideoStream != null && state.VideoStream.Index == -1)
{
return "-sn";
}
// We have media info, but we don't know the stream indexes
// We have media info, but we don't know the stream index
if (state.AudioStream != null && state.AudioStream.Index == -1)
{
return state.IsInputVideo ? "-sn" : string.Empty;
@@ -2229,10 +2327,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.VideoStream != null)
{
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
args += string.Format(
CultureInfo.InvariantCulture,
"-map 0:{0}",
state.VideoStream.Index);
videoStreamIndex);
}
else
{
@@ -2242,23 +2342,27 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream != null)
{
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
if (state.AudioStream.IsExternal)
{
int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1;
int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream);
bool hasExternalGraphicsSubs = state.SubtitleStream != null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& state.SubtitleStream.IsExternal
&& !state.SubtitleStream.IsTextSubtitleStream;
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
args += string.Format(
CultureInfo.InvariantCulture,
" -map {0}:{1}",
externalAudioMapIndex,
externalAudioStream);
audioStreamIndex);
}
else
{
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
state.AudioStream.Index);
audioStreamIndex);
}
}
else
@@ -2273,14 +2377,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
{
int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
state.SubtitleStream.Index);
subtitleStreamIndex);
}
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{
args += " -map 1:0 -sn";
int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 1:{0} -sn",
externalSubtitleStreamIndex);
}
return args;
@@ -2509,7 +2620,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/{2})*{2}:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2",
"scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
maxWidthParam,
maxHeightParam,
scaleVal);
@@ -2553,7 +2664,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*dar)\\,{0})/{1})*{1}:trunc(ow/dar/2)*2",
"scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
maxWidthParam,
scaleVal);
}
@@ -2565,7 +2676,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"scale=trunc(oh*a/{1})*{1}:min(max(iw/dar\\,ih)\\,{0})",
"scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
maxHeightParam,
scaleVal);
}
@@ -2614,7 +2725,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else
{
filter = "scale={0}:trunc({0}/dar/2)*2";
filter = "scale={0}:trunc({0}/a/2)*2";
}
}
@@ -2665,7 +2776,18 @@ namespace MediaBrowser.Controller.MediaEncoding
var args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709";
if (!hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
if (hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
{
args += ",procamp_vaapi=b={2}:c={3}:extra_hw_frames=16";
return string.Format(
CultureInfo.InvariantCulture,
args,
hwTonemapSuffix,
videoFormat ?? "nv12",
options.VppTonemappingBrightness,
options.VppTonemappingContrast);
}
else
{
args += ":tonemap={2}:peak={3}:desat={4}";
@@ -2768,8 +2890,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (hasGraphicalSubs)
{
// [0:s]scale=s=1280x720
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
// [0:s]scale=expr
var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -2867,7 +2989,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doCuTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=cuda");
}
}
@@ -2947,7 +3069,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter);
}
subFilters.Add("hwupload");
subFilters.Add("hwupload=derive_device=cuda");
overlayFilters.Add("overlay_cuda=eof_action=endall:shortest=1:repeatlast=0");
}
}
@@ -2955,7 +3077,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3057,7 +3181,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=16");
mainFilters.Add("format=d3d11");
mainFilters.Add("hwmap=derive_device=opencl");
}
}
@@ -3084,7 +3210,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var memoryOutput = false;
var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
if ((isD3d11vaDecoder && isSwEncoder) || isUploadForOclTonemap)
if (isD3d11vaDecoder && isSwEncoder)
{
memoryOutput = true;
@@ -3096,7 +3222,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// OUTPUT yuv420p surface
if (isSwDecoder && isAmfEncoder)
if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
{
memoryOutput = true;
}
@@ -3111,7 +3237,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
if (isDxInDxOut && !hasSubs)
if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
{
// OUTPUT d3d11(nv12) surface(vram)
// reverse-mapping via d3d11-opencl interop.
@@ -3122,7 +3248,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/* Make sub and overlay filters for subtitle stream */
var subFilters = new List<string>();
var overlayFilters = new List<string>();
if (isDxInDxOut)
if (isDxInDxOut || isUploadForOclTonemap)
{
if (hasSubs)
{
@@ -3143,7 +3269,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter);
}
subFilters.Add("hwupload");
subFilters.Add("hwupload=derive_device=opencl");
overlayFilters.Add("overlay_opencl=eof_action=endall:shortest=1:repeatlast=0");
overlayFilters.Add("hwmap=derive_device=d3d11va:reverse=1");
overlayFilters.Add("format=d3d11");
@@ -3153,7 +3279,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3275,7 +3403,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isD3d11vaDecoder || isQsvDecoder)
@@ -3381,7 +3509,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// qsv requires a fixed pool size.
subFilters.Add("hwupload=extra_hw_frames=32");
// default to 64 otherwise it will fail on certain iGPU.
subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3398,7 +3527,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3469,7 +3600,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isVaapiDecoder || isQsvDecoder)
@@ -3589,7 +3720,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// qsv requires a fixed pool size.
subFilters.Add("hwupload=extra_hw_frames=32");
// default to 64 otherwise it will fail on certain iGPU.
subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3606,7 +3738,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
}
@@ -3650,7 +3784,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var newfilters = new List<string>();
var noOverlay = swFilterChain.OverlayFilters.Count == 0;
newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
newfilters.Add("hwupload");
newfilters.Add("hwupload=derive_device=vaapi");
var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
@@ -3731,7 +3865,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isVaapiDecoder)
@@ -3836,7 +3970,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter);
}
subFilters.Add("hwupload");
subFilters.Add("hwupload=derive_device=vaapi");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3853,7 +3987,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -3925,7 +4061,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isVaapiDecoder)
@@ -3955,7 +4091,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
mainFilters.Add("hwdownload");
mainFilters.Add("format=p010le");
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
@@ -4028,7 +4164,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4124,9 +4262,8 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Join(',', overlayFilters));
var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
var subtitleStreamIndex = state.SubtitleStream.IsExternal
? 0
: state.SubtitleStream.Index;
var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
if (hasSubs)
{
@@ -4147,7 +4284,7 @@ namespace MediaBrowser.Controller.MediaEncoding
filterStr,
mapPrefix,
subtitleStreamIndex,
state.VideoStream.Index,
videoStreamIndex,
mainStr,
subStr,
overlayStr);
@@ -4205,6 +4342,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return videoStream.BitDepth.Value;
}
else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
{
return 8;
@@ -4237,14 +4375,18 @@ namespace MediaBrowser.Controller.MediaEncoding
protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
{
var videoStream = state.VideoStream;
if (videoStream == null)
var mediaSource = state.MediaSource;
if (videoStream == null || mediaSource == null)
{
return null;
}
// Only use alternative encoders for video files.
var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
if (videoType != VideoType.VideoFile)
// HWA decoders can handle both video files and video folders.
var videoType = mediaSource.VideoType;
if (videoType != VideoType.VideoFile
&& videoType != VideoType.Iso
&& videoType != VideoType.Dvd
&& videoType != VideoType.BluRay)
{
return null;
}
@@ -4413,7 +4555,9 @@ namespace MediaBrowser.Controller.MediaEncoding
if (isD3d11Supported && isCodecAvailable)
{
return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
// set -threads 3 to intel d3d11va decoder explicitly. Lower threads may result in dead lock.
// on newer devices such as Xe, the larger the init_pool_size, the longer the initialization time for opencl to derive from d3d11.
return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
}
}
else
@@ -4491,7 +4635,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
&& _mediaEncoder.SupportsFilter("alphasrc");
var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
@@ -4550,7 +4695,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
@@ -4616,7 +4762,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
&& IsOpenclFullSupported()
&& _mediaEncoder.SupportsFilter("alphasrc");
var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsAmf)
@@ -4636,11 +4783,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
}
if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
}
}
if (is8_10bitSwFormatsAmf)
@@ -4677,7 +4819,8 @@ namespace MediaBrowser.Controller.MediaEncoding
&& IsVaapiFullSupported()
&& IsOpenclFullSupported()
&& _mediaEncoder.SupportsFilter("alphasrc");
var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsVaapi)
@@ -4734,7 +4877,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsVt)
@@ -4840,21 +4984,20 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
{
var inputModifier = string.Empty;
var probeSizeArgument = string.Empty;
var analyzeDurationArgument = string.Empty;
string analyzeDurationArgument;
if (state.MediaSource.AnalyzeDurationMs.HasValue)
// Apply -analyzeduration as per the environment variable,
// otherwise ffmpeg will break on certain files due to default value is 0.
// The default value of -probesize is more than enough, so leave it as is.
var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (state.MediaSource.AnalyzeDurationMs > 0)
{
analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToString(CultureInfo.InvariantCulture);
}
else
else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
{
analyzeDurationArgument = string.Empty;
}
if (!string.IsNullOrEmpty(probeSizeArgument))
{
inputModifier += " " + probeSizeArgument;
analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
}
if (!string.IsNullOrEmpty(analyzeDurationArgument))
@@ -4873,12 +5016,21 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier = inputModifier.Trim();
var refererParam = GetRefererParam(state);
if (!string.IsNullOrEmpty(refererParam))
{
inputModifier += " " + refererParam;
}
inputModifier = inputModifier.Trim();
inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
inputModifier = inputModifier.Trim();
if (state.InputProtocol == MediaProtocol.Rtsp)
{
inputModifier += " -rtsp_transport tcp -rtsp_transport udp -rtsp_flags prefer_tcp";
inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
}
if (!string.IsNullOrEmpty(state.InputAudioSync))
@@ -4953,15 +5105,9 @@ namespace MediaBrowser.Controller.MediaEncoding
MediaSourceInfo mediaSource,
string requestedUrl)
{
if (state == null)
{
throw new ArgumentNullException(nameof(state));
}
ArgumentNullException.ThrowIfNull(state);
if (mediaSource == null)
{
throw new ArgumentNullException(nameof(mediaSource));
}
ArgumentNullException.ThrowIfNull(mediaSource);
var path = mediaSource.Path;
var protocol = mediaSource.Protocol;
@@ -5357,12 +5503,22 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
// opus will fail on 44100
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
if (state.OutputAudioSampleRate.HasValue)
// opus only supports specific sampling rates
var sampleRate = state.OutputAudioSampleRate;
if (sampleRate.HasValue)
{
audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
var sampleRateValue = sampleRate.Value switch
{
<= 8000 => 8000,
<= 12000 => 12000,
<= 16000 => 16000,
<= 24000 => 24000,
_ => 48000
};
audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
}
}
@@ -5384,6 +5540,28 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Empty).Trim();
}
public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
{
var index = 0;
var length = mediaStreams.Count;
for (var i = 0; i < length; i++)
{
var currentMediaStream = mediaStreams[i];
if (currentMediaStream == streamToFind)
{
return index;
}
if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
{
index++;
}
}
return -1;
}
public static bool IsCopyCodec(string codec)
{
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);

View File

@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Entities;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
@@ -366,6 +365,28 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
/// <summary>
/// Gets the target video range type.
/// </summary>
public string TargetVideoRangeType
{
get
{
if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec))
{
return VideoStream?.VideoRangeType;
}
var requestedRangeType = GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault();
if (!string.IsNullOrEmpty(requestedRangeType))
{
return requestedRangeType;
}
return null;
}
}
public string TargetVideoCodecTag
{
get
@@ -579,6 +600,26 @@ namespace MediaBrowser.Controller.MediaEncoding
return Array.Empty<string>();
}
public string[] GetRequestedRangeTypes(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.VideoRangeType))
{
return BaseRequest.VideoRangeType.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
}
if (!string.IsNullOrEmpty(codec))
{
var rangetype = BaseRequest.GetOption(codec, "rangetype");
if (!string.IsNullOrEmpty(rangetype))
{
return rangetype.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
}
}
return Array.Empty<string>();
}
public string GetRequestedLevel(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.Level))

View File

@@ -37,6 +37,12 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>The version of encoder.</returns>
Version EncoderVersion { get; }
/// <summary>
/// Whether p key pausing is supported.
/// </summary>
/// <value><c>true</c> if p key pausing is supported, <c>false</c> otherwise.</value>
bool IsPkeyPauseSupported { get; }
/// <summary>
/// Gets a value indicating whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver).
/// </summary>
@@ -141,6 +147,13 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.String.</returns>
string GetInputArgument(string inputFile, MediaSourceInfo mediaSource);
/// <summary>
/// Gets the input argument for an external subtitle file.
/// </summary>
/// <param name="inputFile">The input file.</param>
/// <returns>System.String.</returns>
string GetExternalSubtitleInputArgument(string inputFile);
/// <summary>
/// Gets the time parameter.
/// </summary>

View File

@@ -6,7 +6,8 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -37,11 +38,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the subtitle language encoding parameter.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="language">The language.</param>
/// <param name="protocol">The protocol.</param>
/// <param name="mediaSource">The media source.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken);
Task<string> GetSubtitleFileCharacterSet(MediaStream subtitleStream, string language, MediaSourceInfo mediaSource, CancellationToken cancellationToken);
}
}

View File

@@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.MediaEncoding
percent = 100.0 * currentMs / totalMs;
transcodingPosition = val;
transcodingPosition = TimeSpan.FromMilliseconds(currentMs);
}
}
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))

View File

@@ -38,10 +38,7 @@ namespace MediaBrowser.Controller.Net
protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
ArgumentNullException.ThrowIfNull(logger);
Logger = logger;
}
@@ -77,10 +74,7 @@ namespace MediaBrowser.Controller.Net
/// <returns>Task.</returns>
public Task ProcessMessageAsync(WebSocketMessageInfo message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
ArgumentNullException.ThrowIfNull(message);
if (message.MessageType == StartType)
{

View File

@@ -1,20 +0,0 @@
#pragma warning disable CS1591
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Session;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
public interface ISessionContext
{
Task<SessionInfo> GetSession(object requestContext);
Task<User?> GetUser(object requestContext);
Task<SessionInfo> GetSession(HttpContext requestContext);
Task<User?> GetUser(HttpContext requestContext);
}
}

View File

@@ -6,11 +6,10 @@ using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
public interface IWebSocketConnection
public interface IWebSocketConnection : IAsyncDisposable, IDisposable
{
/// <summary>
/// Occurs when [closed].

View File

@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;

View File

@@ -34,8 +34,8 @@ namespace MediaBrowser.Controller.Providers
public bool IsReplacingImage(ImageType type)
{
return ImageRefreshMode == MetadataRefreshMode.FullRefresh &&
(ReplaceAllImages || ReplaceImages.Contains(type));
return ImageRefreshMode == MetadataRefreshMode.FullRefresh
&& (ReplaceAllImages || ReplaceImages.Contains(type));
}
}
}

View File

@@ -5,6 +5,11 @@ namespace MediaBrowser.Controller.Resolvers
/// </summary>
public enum ResolverPriority
{
/// <summary>
/// The highest priority. Used by plugins to bypass the default server resolvers.
/// </summary>
Plugin = 0,
/// <summary>
/// The first.
/// </summary>

View File

@@ -352,6 +352,6 @@ namespace MediaBrowser.Controller.Session
/// <returns>Task.</returns>
Task RevokeUserTokens(Guid userId, string currentAccessToken);
void CloseIfNeeded(SessionInfo session);
Task CloseIfNeededAsync(SessionInfo session);
}
}

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
@@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Session
/// <summary>
/// Class SessionInfo.
/// </summary>
public sealed class SessionInfo : IDisposable
public sealed class SessionInfo : IAsyncDisposable, IDisposable
{
// 1 second
private const long ProgressIncrement = 10000000;
@@ -380,10 +381,28 @@ namespace MediaBrowser.Controller.Session
{
if (controller is IDisposable disposable)
{
_logger.LogDebug("Disposing session controller {0}", disposable.GetType().Name);
_logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name);
disposable.Dispose();
}
}
}
public async ValueTask DisposeAsync()
{
_disposed = true;
StopAutomaticProgress();
var controllers = SessionControllers.ToList();
foreach (var controller in controllers)
{
if (controller is IAsyncDisposable disposableAsync)
{
_logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name);
await disposableAsync.DisposeAsync().ConfigureAwait(false);
}
}
}
}
}

View File

@@ -549,7 +549,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
if (InitialState.Equals(GroupStateType.Playing))
{
// Group went from playing to waiting state and a pause request occured while waiting.
// Group went from playing to waiting state and a pause request occurred while waiting.
var pauseRequest = new PauseGroupRequest();
pausedState.HandleRequest(pauseRequest, context, Type, session, cancellationToken);
}

View File

@@ -27,9 +27,9 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests
}
/// <summary>
/// Gets the playlist identifiers ot the items.
/// Gets the playlist identifiers of the items.
/// </summary>
/// <value>The playlist identifiers ot the items.</value>
/// <value>The playlist identifiers of the items.</value>
public IReadOnlyList<Guid> PlaylistItemIds { get; }
/// <summary>

View File

@@ -102,7 +102,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
/// <summary>
/// Appends new items to the playlist. The specified order is mantained.
/// Appends new items to the playlist. The specified order is maintained.
/// </summary>
/// <param name="items">The items to add to the playlist.</param>
public void Queue(IReadOnlyList<Guid> items)
@@ -197,7 +197,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
/// <summary>
/// Adds new items to the playlist right after the playing item. The specified order is mantained.
/// Adds new items to the playlist right after the playing item. The specified order is maintained.
/// </summary>
/// <param name="items">The items to add to the playlist.</param>
public void QueueNext(IReadOnlyList<Guid> items)