mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-20 17:14:42 +01:00
Merge branch 'master' into providermanager-cleanup
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
namespace MediaBrowser.Controller.Library
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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].
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user