Merge branch 'master' into websocket

This commit is contained in:
Bond_009
2020-05-02 00:54:04 +02:00
1246 changed files with 23673 additions and 14062 deletions

View File

@@ -7,23 +7,29 @@ namespace MediaBrowser.Controller.Authentication
/// </summary>
public class AuthenticationException : Exception
{
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
/// </summary>
public AuthenticationException() : base()
{
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public AuthenticationException(string message) : base(message)
{
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public AuthenticationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;

View File

@@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Channels
/// </summary>
/// <param name="type">The type.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{DynamicImageInfo}.</returns>
/// <returns>Task{DynamicImageResponse}.</returns>
Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken);
/// <summary>

View File

@@ -1,16 +1,17 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Chapters
{
/// <summary>
/// Interface IChapterManager
/// Interface IChapterManager.
/// </summary>
public interface IChapterManager
{
/// <summary>
/// Saves the chapters.
/// </summary>
void SaveChapters(string itemId, List<ChapterInfo> chapters);
void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
@@ -20,20 +19,12 @@ namespace MediaBrowser.Controller.Drawing
/// <value>The supported input formats.</value>
IReadOnlyCollection<string> SupportedInputFormats { get; }
/// <summary>
/// Gets the image enhancers.
/// </summary>
/// <value>The image enhancers.</value>
IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
/// <summary>
/// Gets a value indicating whether [supports image collage creation].
/// </summary>
/// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value>
bool SupportsImageCollageCreation { get; }
IImageEncoder ImageEncoder { get; set; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
@@ -49,23 +40,6 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>ImageDimensions</returns>
ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info);
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
/// <param name="item">The base item.</param>
/// <param name="info">The information.</param>
/// <param name="updateItem">Whether or not the item info should be updated.</param>
/// <returns>ImageDimensions</returns>
ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem);
/// <summary>
/// Gets the supported enhancers.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns>IEnumerable{IImageEnhancer}.</returns>
IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType);
/// <summary>
/// Gets the image cache tag.
/// </summary>
@@ -75,15 +49,6 @@ namespace MediaBrowser.Controller.Drawing
string GetImageCacheTag(BaseItem item, ItemImageInfo image);
string GetImageCacheTag(BaseItem item, ChapterInfo info);
/// <summary>
/// Gets the image cache tag.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="image">The image.</param>
/// <param name="imageEnhancers">The image enhancers.</param>
/// <returns>Guid.</returns>
string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers);
/// <summary>
/// Processes the image.
/// </summary>
@@ -99,15 +64,6 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>Task.</returns>
Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options);
/// <summary>
/// Gets the enhanced image.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{System.String}.</returns>
Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex);
/// <summary>
/// Gets the supported image output formats.
/// </summary>

View File

@@ -19,8 +19,6 @@ namespace MediaBrowser.Controller.Drawing
return GetSizeEstimate(options);
}
public static IImageProcessor ImageProcessor { get; set; }
private static ImageDimensions GetSizeEstimate(ImageProcessingOptions options)
{
if (options.Width.HasValue && options.Height.HasValue)

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
namespace MediaBrowser.Controller.Drawing
@@ -34,8 +33,6 @@ namespace MediaBrowser.Controller.Drawing
public int Quality { get; set; }
public IReadOnlyCollection<IImageEnhancer> Enhancers { get; set; }
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
public bool AddPlayedIndicator { get; set; }

View File

@@ -198,6 +198,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return true;
}
}
return base.RequiresRefresh();
}

View File

@@ -15,7 +15,6 @@ using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -33,7 +32,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class BaseItem
/// </summary>
public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>
public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem>
{
/// <summary>
/// The supported image extensions
@@ -178,6 +177,7 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public int? TotalBitrate { get; set; }
[JsonIgnore]
public ExtraType? ExtraType { get; set; }
@@ -387,15 +387,12 @@ namespace MediaBrowser.Controller.Entities
while (thisMarker < s1.Length)
{
if (thisMarker >= s1.Length)
{
break;
}
char thisCh = s1[thisMarker];
var thisChunk = new StringBuilder();
bool isNumeric = char.IsDigit(thisCh);
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0])))
while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric)
{
thisChunk.Append(thisCh);
thisMarker++;
@@ -406,7 +403,6 @@ namespace MediaBrowser.Controller.Entities
}
}
var isNumeric = thisChunk.Length > 0 && char.IsDigit(thisChunk[0]);
list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric));
}
@@ -553,7 +549,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public DateTime DateModified { get; set; }
[JsonIgnore]
public DateTime DateLastSaved { get; set; }
[JsonIgnore]
@@ -1330,8 +1325,9 @@ namespace MediaBrowser.Controller.Entities
}
// Use some hackery to get the extra type based on foldername
Enum.TryParse(extraFolderName.Replace(" ", ""), true, out ExtraType extraType);
item.ExtraType = extraType;
item.ExtraType = Enum.TryParse(extraFolderName.Replace(" ", string.Empty), true, out ExtraType extraType)
? extraType
: Model.Entities.ExtraType.Unknown;
return item;
@@ -2194,13 +2190,9 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Do whatever refreshing is necessary when the filesystem pertaining to this item has changed.
/// </summary>
/// <returns>Task.</returns>
public virtual void ChangedExternally()
{
ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions(new DirectoryService(FileSystem))
{
}, RefreshPriority.High);
ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions(new DirectoryService(FileSystem)), RefreshPriority.High);
}
/// <summary>
@@ -2231,7 +2223,6 @@ namespace MediaBrowser.Controller.Entities
existingImage.Width = image.Width;
existingImage.Height = image.Height;
}
else
{
var current = ImageInfos;
@@ -2274,7 +2265,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <param name="type">The type.</param>
/// <param name="index">The index.</param>
/// <returns>Task.</returns>
public void DeleteImage(ImageType type, int index)
{
var info = GetImageInfo(type, index);
@@ -2312,7 +2302,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
/// Validates that images within the item are still on the file system
/// Validates that images within the item are still on the filesystem.
/// </summary>
public bool ValidateImages(IDirectoryService directoryService)
{
@@ -2606,7 +2596,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// This is called before any metadata refresh and returns true if changes were made.
/// </summary>
public virtual bool BeforeMetadataRefresh(bool replaceAllMetdata)
{
@@ -2666,36 +2656,43 @@ namespace MediaBrowser.Controller.Entities
newOptions.ForceSave = true;
ownedItem.Genres = item.Genres;
}
if (!item.Studios.SequenceEqual(ownedItem.Studios, StringComparer.Ordinal))
{
newOptions.ForceSave = true;
ownedItem.Studios = item.Studios;
}
if (!item.ProductionLocations.SequenceEqual(ownedItem.ProductionLocations, StringComparer.Ordinal))
{
newOptions.ForceSave = true;
ownedItem.ProductionLocations = item.ProductionLocations;
}
if (item.CommunityRating != ownedItem.CommunityRating)
{
ownedItem.CommunityRating = item.CommunityRating;
newOptions.ForceSave = true;
}
if (item.CriticRating != ownedItem.CriticRating)
{
ownedItem.CriticRating = item.CriticRating;
newOptions.ForceSave = true;
}
if (!string.Equals(item.Overview, ownedItem.Overview, StringComparison.Ordinal))
{
ownedItem.Overview = item.Overview;
newOptions.ForceSave = true;
}
if (!string.Equals(item.OfficialRating, ownedItem.OfficialRating, StringComparison.Ordinal))
{
ownedItem.OfficialRating = item.OfficialRating;
newOptions.ForceSave = true;
}
if (!string.Equals(item.CustomRating, ownedItem.CustomRating, StringComparison.Ordinal))
{
ownedItem.CustomRating = item.CustomRating;
@@ -2743,7 +2740,7 @@ namespace MediaBrowser.Controller.Entities
{
var list = GetEtagValues(user);
return string.Join("|", list.ToArray()).GetMD5().ToString("N", CultureInfo.InvariantCulture);
return string.Join("|", list).GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
protected virtual List<string> GetEtagValues(User user)
@@ -2786,8 +2783,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
var view = this as IHasCollectionType;
if (view != null)
if (this is IHasCollectionType view)
{
if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
{
@@ -2880,14 +2876,29 @@ namespace MediaBrowser.Controller.Entities
/// <value>The remote trailers.</value>
public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
/// <summary>
/// Get all extras associated with this item, sorted by <see cref="SortName"/>.
/// </summary>
/// <returns>An enumerable containing the items.</returns>
public IEnumerable<BaseItem> GetExtras()
{
return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
return ExtraIds
.Select(LibraryManager.GetItemById)
.Where(i => i != null)
.OrderBy(i => i.SortName);
}
/// <summary>
/// Get all extras with specific types that are associated with this item.
/// </summary>
/// <param name="extraTypes">The types of extras to retrieve.</param>
/// <returns>An enumerable containing the extras.</returns>
public IEnumerable<BaseItem> GetExtras(IReadOnlyCollection<ExtraType> extraTypes)
{
return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null && extraTypes.Contains(i.ExtraType.Value));
return ExtraIds
.Select(LibraryManager.GetItemById)
.Where(i => i != null)
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
}
public IEnumerable<BaseItem> GetTrailers()
@@ -2898,25 +2909,49 @@ namespace MediaBrowser.Controller.Entities
return Array.Empty<BaseItem>();
}
public IEnumerable<BaseItem> GetDisplayExtras()
{
return GetExtras(DisplayExtraTypes);
}
public virtual bool IsHD => Height >= 720;
public bool IsShortcut { get; set; }
public string ShortcutPath { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public Guid[] ExtraIds { get; set; }
public virtual long GetRunTimeTicksForPlayState()
{
return RunTimeTicks ?? 0;
}
// Possible types of extra videos
public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new[] { Model.Entities.ExtraType.BehindTheScenes, Model.Entities.ExtraType.Clip, Model.Entities.ExtraType.DeletedScene, Model.Entities.ExtraType.Interview, Model.Entities.ExtraType.Sample, Model.Entities.ExtraType.Scene };
/// <summary>
/// Extra types that should be counted and displayed as "Special Features" in the UI.
/// </summary>
public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType>
{
Model.Entities.ExtraType.Unknown,
Model.Entities.ExtraType.BehindTheScenes,
Model.Entities.ExtraType.Clip,
Model.Entities.ExtraType.DeletedScene,
Model.Entities.ExtraType.Interview,
Model.Entities.ExtraType.Sample,
Model.Entities.ExtraType.Scene
};
public virtual bool SupportsExternalTransfer => false;
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is BaseItem baseItem && this.Equals(baseItem);
}
/// <inheritdoc />
public bool Equals(BaseItem item) => Object.Equals(Id, item?.Id);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(Id);
}
}

View File

@@ -13,8 +13,10 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public string SeriesPresentationUniqueKey { get; set; }
[JsonIgnore]
public string SeriesName { get; set; }
[JsonIgnore]
public Guid SeriesId { get; set; }
@@ -22,10 +24,12 @@ namespace MediaBrowser.Controller.Entities
{
return SeriesName;
}
public string FindSeriesName()
{
return SeriesName;
}
public string FindSeriesPresentationUniqueKey()
{
return SeriesPresentationUniqueKey;

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -28,7 +30,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class Folder : BaseItem
{
public static IUserManager UserManager { get; set; }
public static IUserViewManager UserViewManager { get; set; }
/// <summary>
@@ -322,10 +323,10 @@ namespace MediaBrowser.Controller.Entities
ProviderManager.OnRefreshProgress(this, 5);
}
//build a dictionary of the current children we have now by Id so we can compare quickly and easily
// Build a dictionary of the current children we have now by Id so we can compare quickly and easily
var currentChildren = GetActualChildrenDictionary();
//create a list for our validated children
// Create a list for our validated children
var newItems = new List<BaseItem>();
cancellationToken.ThrowIfCancellationRequested();
@@ -391,7 +392,7 @@ namespace MediaBrowser.Controller.Entities
var folder = this;
innerProgress.RegisterAction(p =>
{
double newPct = .80 * p + 10;
double newPct = 0.80 * p + 10;
progress.Report(newPct);
ProviderManager.OnRefreshProgress(folder, newPct);
});
@@ -421,7 +422,7 @@ namespace MediaBrowser.Controller.Entities
var folder = this;
innerProgress.RegisterAction(p =>
{
double newPct = .10 * p + 90;
double newPct = 0.10 * p + 90;
progress.Report(newPct);
if (recursive)
{
@@ -620,7 +621,6 @@ namespace MediaBrowser.Controller.Entities
{
EnableImages = false
}
}).TotalRecordCount;
}
@@ -807,11 +807,45 @@ namespace MediaBrowser.Controller.Entities
return false;
}
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;
}
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
{
if (query.ItemIds.Length > 0)
{
return LibraryManager.GetItemsResult(query);
var result = LibraryManager.GetItemsResult(query);
if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
{
result.Items = SortItemsByRequest(query, result.Items);
}
return result;
}
return GetItemsInternal(query);
@@ -823,7 +857,14 @@ namespace MediaBrowser.Controller.Entities
if (query.ItemIds.Length > 0)
{
return LibraryManager.GetItemList(query);
var result = LibraryManager.GetItemList(query);
if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
{
return SortItemsByRequest(query, result);
}
return result;
}
return GetItemsInternal(query).Items;
@@ -1672,7 +1713,6 @@ namespace MediaBrowser.Controller.Entities
{
EnableImages = false
}
});
double unplayedCount = unplayedQueryResult.TotalRecordCount;

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// Marker interface
/// Marker interface.
/// </summary>
public interface IItemByName
{

View File

@@ -4,11 +4,21 @@ namespace MediaBrowser.Controller.Entities
{
public class InternalPeopleQuery
{
/// <summary>
/// Gets or sets the maximum number of items the query should return.
/// </summary>
public int Limit { get; set; }
public Guid ItemId { get; set; }
public string[] PersonTypes { get; set; }
public string[] ExcludePersonTypes { get; set; }
public int? MaxListOrder { get; set; }
public Guid AppearsInItemId { get; set; }
public string NameContains { get; set; }
public InternalPeopleQuery()

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Text.Json.Serialization;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
@@ -135,57 +134,4 @@ namespace MediaBrowser.Controller.Entities
return hasChanges;
}
}
/// <summary>
/// This is the small Person stub that is attached to BaseItems
/// </summary>
public class PersonInfo : IHasProviderIds
{
public PersonInfo()
{
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public Guid ItemId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the role.
/// </summary>
/// <value>The role.</value>
public string Role { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public string Type { get; set; }
/// <summary>
/// Gets or sets the sort order - ascending
/// </summary>
/// <value>The sort order.</value>
public int? SortOrder { get; set; }
public string ImageUrl { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
/// <returns>A <see cref="string" /> that represents this instance.</returns>
public override string ToString()
{
return Name;
}
public bool IsType(string type)
{
return string.Equals(Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(Role, type, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// This is a small Person stub that is attached to BaseItems.
/// </summary>
public sealed class PersonInfo : IHasProviderIds
{
public PersonInfo()
{
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public Guid ItemId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the role.
/// </summary>
/// <value>The role.</value>
public string Role { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public string Type { get; set; }
/// <summary>
/// Gets or sets the ascending sort order.
/// </summary>
/// <value>The sort order.</value>
public int? SortOrder { get; set; }
public string ImageUrl { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
/// <returns>A <see cref="string" /> that represents this instance.</returns>
public override string ToString()
{
return Name;
}
public bool IsType(string type)
{
return string.Equals(Type, type, StringComparison.OrdinalIgnoreCase)
|| string.Equals(Role, type, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -41,10 +41,10 @@ namespace MediaBrowser.Controller.Entities
public override double GetDefaultPrimaryImageAspectRatio()
{
// REVIEW: @bond
if (Width.HasValue && Height.HasValue)
if (Width != 0 && Height != 0)
{
double width = Width.Value;
double height = Height.Value;
double width = Width;
double height = Height;
if (Orientation.HasValue)
{
@@ -67,8 +67,6 @@ namespace MediaBrowser.Controller.Entities
return base.GetDefaultPrimaryImageAspectRatio();
}
public new int? Width { get; set; }
public new int? Height { get; set; }
public string CameraMake { get; set; }
public string CameraModel { get; set; }
public string Software { get; set; }

View File

@@ -60,14 +60,7 @@ namespace MediaBrowser.Controller.Entities
PresetViews = query.PresetViews
});
var itemsArray = result;
var totalCount = itemsArray.Length;
return new QueryResult<BaseItem>
{
TotalRecordCount = totalCount,
Items = itemsArray //TODO Fix The co-variant conversion between Folder[] and BaseItem[], this can generate runtime issues.
};
return UserViewBuilder.SortAndPage(result, null, query, LibraryManager, true);
}
public override int GetChildCount(User user)

View File

@@ -7,7 +7,6 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;

View File

@@ -1,3 +1,4 @@
using System;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.Extensions
@@ -7,30 +8,57 @@ namespace MediaBrowser.Controller.Extensions
/// </summary>
public static class ConfigurationExtensions
{
/// <summary>
/// The key for a setting that indicates whether the application should host web client content.
/// </summary>
public const string HostWebClientKey = "hostwebclient";
/// <summary>
/// The key for the FFmpeg probe size option.
/// </summary>
public const string FfmpegProbeSizeKey = "FFmpeg:probesize";
/// <summary>
/// The key for the FFmpeg analyse duration option.
/// The key for the FFmpeg analyze duration option.
/// </summary>
public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration";
/// <summary>
/// Retrieves the FFmpeg probe size from the <see cref="IConfiguration" />.
/// The key for a setting that indicates whether playlists should allow duplicate entries.
/// </summary>
/// <param name="configuration">This configuration.</param>
public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates";
/// <summary>
/// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
/// </summary>
/// <param name="configuration">The configuration to retrieve the value from.</param>
/// <returns>The parsed config value.</returns>
/// <exception cref="FormatException">The config value is not a valid bool string. See <see cref="bool.Parse(string)"/>.</exception>
public static bool HostWebClient(this IConfiguration configuration)
=> configuration.GetValue<bool>(HostWebClientKey);
/// <summary>
/// Gets the FFmpeg probe size from the <see cref="IConfiguration" />.
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>The FFmpeg probe size option.</returns>
public static string GetFFmpegProbeSize(this IConfiguration configuration)
=> configuration[FfmpegProbeSizeKey];
/// <summary>
/// Retrieves the FFmpeg analyse duration from the <see cref="IConfiguration" />.
/// Gets the FFmpeg analyze duration from the <see cref="IConfiguration" />.
/// </summary>
/// <param name="configuration">This configuration.</param>
/// <returns>The FFmpeg analyse duration option.</returns>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>The FFmpeg analyze duration option.</returns>
public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration)
=> configuration[FfmpegAnalyzeDurationKey];
/// <summary>
/// Gets a value indicating whether playlists should allow duplicate entries from the <see cref="IConfiguration"/>.
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>True if playlists should allow duplicates, otherwise false.</returns>
public static bool DoPlaylistsAllowDuplicates(this IConfiguration configuration)
=> configuration.GetValue<bool>(PlaylistsAllowDuplicatesKey);
}
}

View File

@@ -65,23 +65,32 @@ namespace MediaBrowser.Controller
/// <summary>
/// Gets the local API URL.
/// </summary>
/// <param name="cancellationToken">Token to cancel the request if needed.</param>
/// <param name="forceHttp">Whether to force usage of plain HTTP protocol.</param>
/// <value>The local API URL.</value>
Task<string> GetLocalApiUrl(CancellationToken cancellationToken);
Task<string> GetLocalApiUrl(CancellationToken cancellationToken, bool forceHttp = false);
/// <summary>
/// Gets the local API URL.
/// </summary>
/// <param name="hostname">The hostname.</param>
/// <param name="forceHttp">Whether to force usage of plain HTTP protocol.</param>
/// <returns>The local API URL.</returns>
string GetLocalApiUrl(ReadOnlySpan<char> hostname);
string GetLocalApiUrl(ReadOnlySpan<char> hostname, bool forceHttp = false);
/// <summary>
/// Gets the local API URL.
/// </summary>
/// <param name="address">The IP address.</param>
/// <param name="forceHttp">Whether to force usage of plain HTTP protocol.</param>
/// <returns>The local API URL.</returns>
string GetLocalApiUrl(IPAddress address);
string GetLocalApiUrl(IPAddress address, bool forceHttp = false);
/// <summary>
/// Open a URL in an external browser window.
/// </summary>
/// <param name="url">The URL to open.</param>
/// <exception cref="NotSupportedException"><see cref="CanLaunchWebBrowser"/> is false.</exception>
void LaunchUrl(string url);
void EnableLoopback(string appName);

View File

@@ -71,7 +71,12 @@ namespace MediaBrowser.Controller
string UserConfigurationDirectoryPath { get; }
/// <summary>
/// Gets the internal metadata path.
/// Gets the default internal metadata path.
/// </summary>
string DefaultInternalMetadataPath { get; }
/// <summary>
/// Gets the internal metadata path, either a custom path or the default.
/// </summary>
/// <value>The internal metadata path.</value>
string InternalMetadataPath { get; }

View File

@@ -157,7 +157,8 @@ namespace MediaBrowser.Controller.Library
/// <param name="introProviders">The intro providers.</param>
/// <param name="itemComparers">The item comparers.</param>
/// <param name="postscanTasks">The postscan tasks.</param>
void AddParts(IEnumerable<IResolverIgnoreRule> rules,
void AddParts(
IEnumerable<IResolverIgnoreRule> rules,
IEnumerable<IItemResolver> resolvers,
IEnumerable<IIntroProvider> introProviders,
IEnumerable<IBaseItemComparer> itemComparers,
@@ -349,9 +350,6 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if [is audio file] [the specified path]; otherwise, <c>false</c>.</returns>
bool IsAudioFile(string path);
bool IsAudioFile(string path, LibraryOptions libraryOptions);
bool IsVideoFile(string path, LibraryOptions libraryOptions);
/// <summary>
/// Gets the season number from path.
/// </summary>

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

View File

@@ -1,6 +1,6 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.Extensions;
namespace MediaBrowser.Controller.Library

View File

@@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="name">The name.</param>
/// <param name="logger">The logger.</param>
public Profiler(string name, ILogger logger)
public Profiler(string name, ILogger<Profiler> logger)
{
this._name = name;

View File

@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</ProjectGuid>
</PropertyGroup>
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
@@ -8,7 +13,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
@@ -26,4 +32,16 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>

View File

@@ -78,8 +78,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(hwType)
&& encodingOptions.EnableHardwareEncoding
&& codecMap.ContainsKey(hwType)
&& CheckVaapi(state, hwType, encodingOptions))
&& codecMap.ContainsKey(hwType))
{
var preferredEncoder = codecMap[hwType];
@@ -93,23 +92,6 @@ namespace MediaBrowser.Controller.MediaEncoding
return defaultEncoder;
}
private bool CheckVaapi(EncodingJobInfo state, string hwType, EncodingOptions encodingOptions)
{
if (!string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
// No vaapi requested, return OK.
return true;
}
if (string.IsNullOrEmpty(encodingOptions.VaapiDevice))
{
// No device specified, return OK.
return true;
}
return IsVaapiSupported(state);
}
private bool IsVaapiSupported(EncodingJobInfo state)
{
var videoStream = state.VideoStream;
@@ -424,7 +406,13 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
{
return "aac -strict experimental";
// Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
{
return "libfdk_aac";
}
return "aac";
}
if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
@@ -460,40 +448,37 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hwOutputFormat = "vaapi";
if (hasGraphicalSubs)
{
hwOutputFormat = "yuv420p";
}
arg.Append("-hwaccel vaapi -hwaccel_output_format ")
.Append(hwOutputFormat)
arg.Append("-hwaccel vaapi -hwaccel_output_format vaapi")
.Append(" -vaapi_device ")
.Append(encodingOptions.VaapiDevice)
.Append(' ');
}
if (state.IsVideoRequest
if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions);
var outputVideoCodec = GetVideoEncoder(state, encodingOptions);
if (encodingOptions.EnableHardwareEncoding && outputVideoCodec.Contains("qsv", StringComparison.OrdinalIgnoreCase))
{
if (!string.IsNullOrEmpty(videoDecoder) && videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase))
{
arg.Append("-hwaccel qsv ");
}
else
{
arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
}
}
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
arg.Append(videoDecoder + " ");
if (!hasTextSubs)
{
// While using QSV encoder
if ((outputVideoCodec ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1)
{
// While using QSV decoder
if ((videoDecoder ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1)
{
arg.Append("-hwaccel qsv ");
}
// While using SW decoder
else
{
arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
}
}
}
}
arg.Append("-i ")
@@ -503,17 +488,6 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{
if (state.VideoStream != null && state.VideoStream.Width.HasValue)
{
// This is hacky but not sure how to get the exact subtitle resolution
int height = Convert.ToInt32(state.VideoStream.Width.Value / 16.0 * 9.0);
arg.Append(" -canvas_size ")
.Append(state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture))
.Append(':')
.Append(height.ToString(CultureInfo.InvariantCulture));
}
var subtitlePath = state.SubtitleStream.Path;
if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase))
@@ -653,7 +627,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(fallbackFontPath));
// using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), GetType().Namespace + ".DroidSansFallback.ttf"))
// {
// using (var fileStream = _fileSystem.GetFileStream(fallbackFontPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
// using (var fileStream = new FileStream(fallbackFontPath, FileMode.Create, FileAccess.Write, FileShare.Read))
// {
// stream.CopyTo(fileStream);
// }
@@ -1546,9 +1520,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
/// Gets the internal graphical subtitle param.
/// Gets the graphical subtitle param.
/// </summary>
public string GetGraphicalSubtitleParam(EncodingJobInfo state, EncodingOptions options, string outputVideoCodec)
public string GetGraphicalSubtitleParam(
EncodingJobInfo state,
EncodingOptions options,
string outputVideoCodec)
{
var outputSizeParam = string.Empty;
@@ -1562,53 +1539,77 @@ namespace MediaBrowser.Controller.MediaEncoding
{
outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
var index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = "," + outputSizeParam.Substring(index);
}
outputSizeParam = "," + outputSizeParam.Substring(index);
}
else
{
var index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = "," + outputSizeParam.Substring(index);
}
}
}
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
&& outputSizeParam.Length == 0)
{
outputSizeParam = ",format=nv12|vaapi,hwupload";
// Add parameters to use VAAPI with burn-in subttiles (GH issue #642)
if (state.SubtitleStream != null
&& state.SubtitleStream.IsTextSubtitleStream
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) {
outputSizeParam += ",hwmap=mode=read+write+direct";
else
{
index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = "," + outputSizeParam.Substring(index);
}
else
{
index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = "," + outputSizeParam.Substring(index);
}
}
}
}
}
var videoSizeParam = string.Empty;
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
// Setup subtitle scaling
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
{
// force_original_aspect_ratio=decrease
// Enable decreasing output video width or height if necessary to keep the original aspect ratio
videoSizeParam = string.Format(
CultureInfo.InvariantCulture,
"scale={0}:{1}",
"scale={0}:{1}:force_original_aspect_ratio=decrease",
state.VideoStream.Width.Value,
state.VideoStream.Height.Value);
//For QSV, feed it into hardware encoder now
// For QSV, feed it into hardware encoder now
if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
videoSizeParam += ",hwupload=extra_hw_frames=64";
}
// For VAAPI and CUVID decoder
// these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
// thus needs to be manually adjusted.
if ((IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|| (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
{
var videoStream = state.VideoStream;
var inputWidth = videoStream?.Width;
var inputHeight = videoStream?.Height;
var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
if (width.HasValue && height.HasValue)
{
videoSizeParam = string.Format(
CultureInfo.InvariantCulture,
"scale={0}:{1}:force_original_aspect_ratio=decrease",
width.Value,
height.Value);
}
}
}
var mapPrefix = state.SubtitleStream.IsExternal ?
@@ -1619,27 +1620,50 @@ namespace MediaBrowser.Controller.MediaEncoding
? 0
: state.SubtitleStream.Index;
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
// Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"";
if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
// When the input may or may not be hardware VAAPI decodable
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
/*
[base]: HW scaling video to OutputSize
[sub]: SW scaling subtitle to FixedOutputSize
[base][sub]: SW overlay
*/
outputSizeParam = outputSizeParam.TrimStart(',');
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
/*
[base]: SW scaling video to OutputSize
[sub]: SW scaling subtitle to FixedOutputSize
[base][sub]: SW overlay
*/
outputSizeParam = outputSizeParam.TrimStart(',');
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
}
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
/*
QSV in FFMpeg can now setup hardware overlay for transcodes.
For software decoding and hardware encoding option, frames must be hwuploaded into hardware
with fixed frame size.
with fixed frame size.
*/
if (!string.IsNullOrEmpty(videoDecoder) && videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase))
{
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
}
else
}
else
{
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwupload=extra_hw_frames=64[v];[v][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
}
}
}
return string.Format(
CultureInfo.InvariantCulture,
@@ -1688,7 +1712,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight));
}
public List<string> GetScalingFilters(int? videoWidth,
public List<string> GetScalingFilters(EncodingJobInfo state,
int? videoWidth,
int? videoHeight,
Video3DFormat? threedFormat,
string videoDecoder,
@@ -1707,7 +1732,9 @@ namespace MediaBrowser.Controller.MediaEncoding
requestedMaxWidth,
requestedMaxHeight);
if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) || (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs)
&& width.HasValue
&& height.HasValue)
{
@@ -1731,13 +1758,13 @@ namespace MediaBrowser.Controller.MediaEncoding
vaapi_or_qsv,
outputWidth,
outputHeight));
}
else
}
else
{
filters.Add(string.Format(CultureInfo.InvariantCulture, "scale_{0}=format=nv12", vaapi_or_qsv));
}
}
else if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
&& width.HasValue
&& height.HasValue)
{
@@ -1941,8 +1968,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetOutputSizeParam(
EncodingJobInfo state,
EncodingOptions options,
string outputVideoCodec,
bool allowTimeStampCopy = true)
string outputVideoCodec)
{
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
@@ -1951,42 +1977,61 @@ namespace MediaBrowser.Controller.MediaEncoding
var videoStream = state.VideoStream;
var filters = new List<string>();
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
var hwType = options.HardwareAccelerationType ?? string.Empty;
if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding )
{
filters.Add("hwdownload");
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
var inputWidth = videoStream?.Width;
var inputHeight = videoStream?.Height;
var threeDFormat = state.MediaSource.Video3DFormat;
// If transcoding from 10 bit, transform colour spaces too
if (!string.IsNullOrEmpty(videoStream.PixelFormat)
&& videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=p010le");
filters.Add("format=nv12");
}
else
{
filters.Add("format=nv12");
}
}
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
// When the input may or may not be hardware VAAPI decodable
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
}
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
// If we are software decoding, and hardware encoding
if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
&& (string.IsNullOrEmpty(videoDecoder) || !videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)))
// When the input may or may not be hardware QSV decodable
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=nv12|qsv");
filters.Add("hwupload=extra_hw_frames=64");
if (!hasTextSubs)
{
filters.Add("format=nv12|qsv");
filters.Add("hwupload=extra_hw_frames=64");
}
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
var codec = videoStream.Codec.ToLowerInvariant();
var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
|| videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
// Assert 10-bit hardware VAAPI decodable
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
{
/*
Download data from GPU to CPU as p010le format.
Colorspace conversion is unnecessary here as libx264 will handle it.
If this step is missing, it will fail on AMD but not on intel.
*/
filters.Add("hwdownload");
filters.Add("format=p010le");
}
// Assert 8-bit hardware VAAPI decodable
else if (!isColorDepth10)
{
filters.Add("hwdownload");
filters.Add("format=nv12");
}
}
// Add hardware deinterlace filter before scaling filter
if (state.DeInterlace("h264", true))
{
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
@@ -1995,17 +2040,23 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv"));
if (!hasTextSubs)
{
filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv"));
}
}
}
if ((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true))
&& !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
// Add software deinterlace filter before scaling filter
if (((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true))
&& !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|| (hasTextSubs && state.DeInterlace("h264", true) && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)))
{
var inputFramerate = videoStream?.RealFrameRate;
// If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
if (string.Equals(options.DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
{
filters.Add("yadif=1:-1:0");
}
@@ -2015,11 +2066,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
var inputWidth = videoStream?.Width;
var inputHeight = videoStream?.Height;
var threeDFormat = state.MediaSource.Video3DFormat;
// Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
filters.AddRange(GetScalingFilters(inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
// Add parameters to use VAAPI with burn-in text subttiles (GH issue #642)
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
if (state.SubtitleStream != null
&& state.SubtitleStream.IsTextSubtitleStream
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
{
// Test passed on Intel and AMD gfx
filters.Add("hwmap=mode=read+write");
filters.Add("format=nv12");
}
}
var output = string.Empty;
@@ -2037,11 +2098,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
filters.Add("hwmap");
}
if (allowTimeStampCopy)
{
output += " -copyts";
}
}
if (filters.Count > 0)
@@ -2218,7 +2274,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
inputModifier += " " + videoDecoder;
if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1)
if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
{
var videoStream = state.VideoStream;
var inputWidth = videoStream?.Width;
@@ -2227,7 +2283,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1
if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
&& width.HasValue
&& height.HasValue)
{
@@ -2491,7 +2547,7 @@ namespace MediaBrowser.Controller.MediaEncoding
encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
return null;
}
return "-c:v h264_qsv ";
return "-c:v h264_qsv";
}
break;
case "hevc":
@@ -2499,19 +2555,19 @@ namespace MediaBrowser.Controller.MediaEncoding
if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
{
//return "-c:v hevc_qsv -load_plugin hevc_hw ";
return "-c:v hevc_qsv ";
return "-c:v hevc_qsv";
}
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg2_qsv ";
return "-c:v mpeg2_qsv";
}
break;
case "vc1":
if (_mediaEncoder.SupportsDecoder("vc1_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
{
return "-c:v vc1_qsv ";
return "-c:v vc1_qsv";
}
break;
}
@@ -2525,32 +2581,38 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
return "-c:v h264_cuvid ";
// cuvid decoder does not support 10-bit input
if ((videoStream.BitDepth ?? 8) > 8)
{
encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
return null;
}
return "-c:v h264_cuvid";
}
break;
case "hevc":
case "h265":
if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
{
return "-c:v hevc_cuvid ";
return "-c:v hevc_cuvid";
}
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg2_cuvid ";
return "-c:v mpeg2_cuvid";
}
break;
case "vc1":
if (_mediaEncoder.SupportsDecoder("vc1_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
{
return "-c:v vc1_cuvid ";
return "-c:v vc1_cuvid";
}
break;
case "mpeg4":
if (_mediaEncoder.SupportsDecoder("mpeg4_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg4_cuvid ";
return "-c:v mpeg4_cuvid";
}
break;
}
@@ -2564,38 +2626,38 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
return "-c:v h264_mediacodec ";
return "-c:v h264_mediacodec";
}
break;
case "hevc":
case "h265":
if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
{
return "-c:v hevc_mediacodec ";
return "-c:v hevc_mediacodec";
}
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg2_mediacodec ";
return "-c:v mpeg2_mediacodec";
}
break;
case "mpeg4":
if (_mediaEncoder.SupportsDecoder("mpeg4_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg4_mediacodec ";
return "-c:v mpeg4_mediacodec";
}
break;
case "vp8":
if (_mediaEncoder.SupportsDecoder("vp8_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
{
return "-c:v vp8_mediacodec ";
return "-c:v vp8_mediacodec";
}
break;
case "vp9":
if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
{
return "-c:v vp9_mediacodec ";
return "-c:v vp9_mediacodec";
}
break;
}
@@ -2609,25 +2671,25 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
return "-c:v h264_mmal ";
return "-c:v h264_mmal";
}
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg2_mmal ";
return "-c:v mpeg2_mmal";
}
break;
case "mpeg4":
if (_mediaEncoder.SupportsDecoder("mpeg4_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg4_mmal ";
return "-c:v mpeg4_mmal";
}
break;
case "vc1":
if (_mediaEncoder.SupportsDecoder("vc1_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
{
return "-c:v vc1_mmal ";
return "-c:v vc1_mmal";
}
break;
}
@@ -2637,27 +2699,14 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
if(Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1))
if (Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1))
return "-hwaccel d3d11va";
else
return "-hwaccel dxva2";
}
switch (videoStream.Codec.ToLowerInvariant())
else
{
case "avc":
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_amf") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
return "-c:v h264_amf";
}
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("hevc_amf") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
return "-c:v mpeg2_mmal";
}
break;
return "-hwaccel vaapi";
}
}
}
@@ -2770,7 +2819,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!state.RunTimeTicks.HasValue)
{
args += " -flags -global_header -fflags +genpts";
args += " -fflags +genpts";
}
}
else
@@ -2785,14 +2834,27 @@ namespace MediaBrowser.Controller.MediaEncoding
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasCopyTs = false;
// Add resolution params, if specified
if (!hasGraphicalSubs)
{
var outputSizeParam = GetOutputSizeParam(state, encodingOptions, videoCodec);
args += outputSizeParam;
hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
}
// This is for graphical subs
if (hasGraphicalSubs)
{
var graphicalSubtitleParam = GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
args += graphicalSubtitleParam;
hasCopyTs = graphicalSubtitleParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
}
if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
{
if (!hasCopyTs)
@@ -2800,13 +2862,12 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " -copyts";
}
args += " -avoid_negative_ts disabled -start_at_zero";
}
args += " -avoid_negative_ts disabled";
// This is for internal graphical subs
if (hasGraphicalSubs)
{
args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
{
args += " -start_at_zero";
}
}
var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
@@ -2815,11 +2876,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
args += " " + qualityParam.Trim();
}
if (!state.RunTimeTicks.HasValue)
{
args += " -flags -global_header";
}
}
if (!string.IsNullOrEmpty(state.OutputVideoSync))
@@ -2917,6 +2973,5 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Empty,
string.Empty).Trim();
}
}
}

View File

@@ -9,8 +9,8 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -316,11 +316,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
{
var size = new ImageDimensions
{
Width = VideoStream.Width.Value,
Height = VideoStream.Height.Value
};
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
var newSize = DrawingUtils.Resize(size,
BaseRequest.Width ?? 0,
@@ -346,11 +342,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
{
var size = new ImageDimensions
{
Width = VideoStream.Width.Value,
Height = VideoStream.Height.Value
};
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
var newSize = DrawingUtils.Resize(size,
BaseRequest.Width ?? 0,
@@ -665,7 +657,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
public IProgress<double> Progress { get; set; }
public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
Progress.Report(percentComplete.Value);
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Services;

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -12,6 +14,6 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Refreshes the chapter images.
/// </summary>
Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
}
}

View File

@@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.MediaEncoding
@@ -90,6 +89,15 @@ namespace MediaBrowser.Controller.MediaEncoding
framerate = val;
}
}
else if (part.StartsWith("fps=", StringComparison.OrdinalIgnoreCase))
{
var rate = part.Split(new[] { '=' }, 2)[^1];
if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
{
framerate = val;
}
}
else if (state.RunTimeTicks.HasValue &&
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
{
@@ -146,7 +154,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (framerate.HasValue || percent.HasValue)
{
state.ReportTranscodingProgress(transcodingPosition, 0, percent, 0, bitRate);
state.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
}
}
}

View File

@@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// The _active connections
/// </summary>
protected readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> ActiveConnections =
private readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> _activeConnections =
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
/// <summary>
@@ -98,9 +98,9 @@ namespace MediaBrowser.Controller.Net
InitialDelayMs = dueTimeMs
};
lock (ActiveConnections)
lock (_activeConnections)
{
ActiveConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>(message.Connection, cancellationTokenSource, state));
_activeConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>(message.Connection, cancellationTokenSource, state));
}
}
@@ -108,9 +108,9 @@ namespace MediaBrowser.Controller.Net
{
Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>[] tuples;
lock (ActiveConnections)
lock (_activeConnections)
{
tuples = ActiveConnections
tuples = _activeConnections
.Where(c =>
{
if (c.Item1.State == WebSocketState.Open && !c.Item2.IsCancellationRequested)
@@ -177,9 +177,9 @@ namespace MediaBrowser.Controller.Net
/// <param name="message">The message.</param>
private void Stop(WebSocketMessageInfo message)
{
lock (ActiveConnections)
lock (_activeConnections)
{
var connection = ActiveConnections.FirstOrDefault(c => c.Item1 == message.Connection);
var connection = _activeConnections.FirstOrDefault(c => c.Item1 == message.Connection);
if (connection != null)
{
@@ -209,9 +209,9 @@ namespace MediaBrowser.Controller.Net
//TODO Investigate and properly fix.
}
lock (ActiveConnections)
lock (_activeConnections)
{
ActiveConnections.Remove(connection);
_activeConnections.Remove(connection);
}
}
@@ -223,9 +223,9 @@ namespace MediaBrowser.Controller.Net
{
if (dispose)
{
lock (ActiveConnections)
lock (_activeConnections)
{
foreach (var connection in ActiveConnections.ToArray())
foreach (var connection in _activeConnections.ToArray())
{
DisposeConnection(connection);
}

View File

@@ -1,3 +1,5 @@
#nullable enable
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
@@ -7,6 +9,6 @@ namespace MediaBrowser.Controller.Net
public interface IAuthService
{
void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues);
User Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues);
User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues);
}
}

View File

@@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Controller.Net
@@ -66,7 +64,7 @@ namespace MediaBrowser.Controller.Net
/// <param name="path">The path.</param>
/// <param name="fileShare">The file share.</param>
/// <returns>System.Object.</returns>
Task<object> GetStaticFileResult(IRequest requestContext, string path, FileShareMode fileShare = FileShareMode.Read);
Task<object> GetStaticFileResult(IRequest requestContext, string path, FileShare fileShare = FileShare.Read);
/// <summary>
/// Gets the static file result.

View File

@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// Inits this instance.
/// </summary>
void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listener, IEnumerable<string> urlPrefixes);
void Init(IEnumerable<Type> serviceTypes, IEnumerable<IWebSocketListener> listener, IEnumerable<string> urlPrefixes);
/// <summary>
/// If set, all requests will respond with this message

View File

@@ -2,20 +2,36 @@ using System;
namespace MediaBrowser.Controller.Net
{
/// <summary>
/// The exception that is thrown when a user is authenticated, but not authorized to access a requested resource.
/// </summary>
public class SecurityException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="SecurityException"/> class.
/// </summary>
public SecurityException()
: base()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SecurityException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public SecurityException(string message)
: base(message)
{
}
public SecurityExceptionType SecurityExceptionType { get; set; }
}
public enum SecurityExceptionType
{
Unauthenticated = 0,
ParentalControl = 1
/// <summary>
/// Initializes a new instance of the <see cref="SecurityException"/> class.
/// </summary>
/// <param name="message">The message that describes the error</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public SecurityException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
namespace MediaBrowser.Controller.Net
{
public class StaticResultOptions
@@ -24,12 +22,12 @@ namespace MediaBrowser.Controller.Net
public string Path { get; set; }
public long? ContentLength { get; set; }
public FileShareMode FileShare { get; set; }
public FileShare FileShare { get; set; }
public StaticResultOptions()
{
ResponseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
FileShare = FileShareMode.Read;
FileShare = FileShare.Read;
}
}

View File

@@ -6,30 +6,34 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence
{
/// <summary>
/// Interface IDisplayPreferencesRepository
/// Interface IDisplayPreferencesRepository.
/// </summary>
public interface IDisplayPreferencesRepository : IRepository
{
/// <summary>
/// Saves display preferences for an item
/// Saves display preferences for an item.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="client">The client.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client,
CancellationToken cancellationToken);
void SaveDisplayPreferences(
DisplayPreferences displayPreferences,
string userId,
string client,
CancellationToken cancellationToken);
/// <summary>
/// Saves all display preferences for a user
/// Saves all display preferences for a user.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId,
CancellationToken cancellationToken);
void SaveAllDisplayPreferences(
IEnumerable<DisplayPreferences> displayPreferences,
Guid userId,
CancellationToken cancellationToken);
/// <summary>
/// Gets the display preferences.
/// </summary>

View File

@@ -24,8 +24,7 @@ namespace MediaBrowser.Controller.Persistence
/// Deletes the item.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
void DeleteItem(Guid id, CancellationToken cancellationToken);
void DeleteItem(Guid id);
/// <summary>
/// Saves the items.
@@ -61,7 +60,7 @@ namespace MediaBrowser.Controller.Persistence
/// <summary>
/// Saves the chapters.
/// </summary>
void SaveChapters(Guid id, List<ChapterInfo> chapters);
void SaveChapters(Guid id, IReadOnlyList<ChapterInfo> chapters);
/// <summary>
/// Gets the media streams.
@@ -169,4 +168,3 @@ namespace MediaBrowser.Controller.Persistence
List<string> GetAllArtistNames();
}
}

View File

@@ -1,5 +1,4 @@
using System;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence
{

View File

@@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Playlists
/// <param name="itemIds">The item ids.</param>
/// <param name="userId">The user identifier.</param>
/// <returns>Task.</returns>
void AddToPlaylist(string playlistId, IEnumerable<Guid> itemIds, Guid userId);
void AddToPlaylist(string playlistId, ICollection<Guid> itemIds, Guid userId);
/// <summary>
/// Removes from playlist.

View File

@@ -4,16 +4,22 @@ using System.Threading.Tasks;
namespace MediaBrowser.Controller.Plugins
{
/// <summary>
/// Interface IServerEntryPoint
/// Represents an entry point for a module in the application. This interface is scanned for automatically and
/// provides a hook to initialize the module at application start.
/// The entry point can additionally be flagged as a pre-startup task by implementing the
/// <see cref="IRunBeforeStartup"/> interface.
/// </summary>
public interface IServerEntryPoint : IDisposable
{
/// <summary>
/// Runs this instance.
/// Run the initialization for this module. This method is invoked at application start.
/// </summary>
Task RunAsync();
}
/// <summary>
/// Indicates that a <see cref="IServerEntryPoint"/> should be invoked as a pre-startup task.
/// </summary>
public interface IRunBeforeStartup
{

View File

@@ -16,6 +16,7 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <value>The artist provider ids.</value>
public Dictionary<string, string> ArtistProviderIds { get; set; }
public List<SongInfo> SongInfos { get; set; }
public AlbumInfo()

View File

@@ -2,6 +2,5 @@ namespace MediaBrowser.Controller.Providers
{
public class BoxSetInfo : ItemLookupInfo
{
}
}

View File

@@ -26,7 +26,6 @@ namespace MediaBrowser.Controller.Providers
{
entries = _fileSystem.GetFileSystemEntries(path).ToArray();
//_cache.TryAdd(path, entries);
_cache[path] = entries;
}
@@ -56,7 +55,6 @@ namespace MediaBrowser.Controller.Providers
if (file != null && file.Exists)
{
//_fileCache.TryAdd(path, file);
_fileCache[path] = file;
}
else
@@ -66,15 +64,12 @@ namespace MediaBrowser.Controller.Providers
}
return file;
//return _fileSystem.GetFileInfo(path);
}
public List<string> GetFilePaths(string path)
{
return GetFilePaths(path, false);
}
public IReadOnlyList<string> GetFilePaths(string path)
=> GetFilePaths(path, false);
public List<string> GetFilePaths(string path, bool clearCache)
public IReadOnlyList<string> GetFilePaths(string path, bool clearCache)
{
if (clearCache || !_filePathCache.TryGetValue(path, out List<string> result))
{

View File

@@ -1,10 +0,0 @@
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
public class DynamicImageInfo
{
public string ImageId { get; set; }
public ImageType Type { get; set; }
}
}

View File

@@ -8,9 +8,13 @@ namespace MediaBrowser.Controller.Providers
public class DynamicImageResponse
{
public string Path { get; set; }
public MediaProtocol Protocol { get; set; }
public Stream Stream { get; set; }
public ImageFormat Format { get; set; }
public bool HasImage { get; set; }
public void SetFormatFromMimeType(string mimeType)

View File

@@ -10,6 +10,7 @@ namespace MediaBrowser.Controller.Providers
public int? IndexNumberEnd { get; set; }
public bool IsMissingEpisode { get; set; }
public string SeriesDisplayOrder { get; set; }
public EpisodeInfo()

View File

@@ -1,15 +0,0 @@
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
public class ExtraInfo
{
public string Path { get; set; }
public LocationType LocationType { get; set; }
public bool IsDownloadable { get; set; }
public ExtraType ExtraType { get; set; }
}
}

View File

@@ -1,9 +0,0 @@
namespace MediaBrowser.Controller.Providers
{
public enum ExtraSource
{
Local = 1,
Metadata = 2,
Remote = 3
}
}

View File

@@ -13,7 +13,7 @@ namespace MediaBrowser.Controller.Providers
where TItemType : BaseItem
{
/// <summary>
/// Fetches the asynchronous.
/// Fetches the metadata asynchronously.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="options">The options.</param>

View File

@@ -6,10 +6,13 @@ namespace MediaBrowser.Controller.Providers
public interface IDirectoryService
{
FileSystemMetadata[] GetFileSystemEntries(string path);
List<FileSystemMetadata> GetFiles(string path);
FileSystemMetadata GetFile(string path);
List<string> GetFilePaths(string path);
List<string> GetFilePaths(string path, bool clearCache);
IReadOnlyList<string> GetFilePaths(string path);
IReadOnlyList<string> GetFilePaths(string path, bool clearCache);
}
}

View File

@@ -1,20 +0,0 @@
using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Providers
{
public interface IExtrasProvider
{
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
string Name { get; }
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
bool Supports(BaseItem item);
}
}

View File

@@ -1,7 +1,7 @@
namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// This is a marker interface that will cause a provider to run even if IsLocked=true
/// This is a marker interface that will cause a provider to run even if an item is locked from changes.
/// </summary>
public interface IForcedProvider
{

View File

@@ -1,61 +0,0 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
public interface IImageEnhancer
{
/// <summary>
/// Return true only if the given image for the given item will be enhanced by this enhancer.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns><c>true</c> if this enhancer will enhance the supplied image for the supplied item, <c>false</c> otherwise</returns>
bool Supports(BaseItem item, ImageType imageType);
/// <summary>
/// Gets the priority or order in which this enhancer should be run.
/// </summary>
/// <value>The priority.</value>
MetadataProviderPriority Priority { get; }
/// <summary>
/// Return a key incorporating all configuration information related to this item
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns>Cache key relating to the current state of this item and configuration</returns>
string GetConfigurationCacheKey(BaseItem item, ImageType imageType);
/// <summary>
/// Gets the size of the enhanced image.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <param name="originalImageSize">Size of the original image.</param>
/// <returns>ImageSize.</returns>
ImageDimensions GetEnhancedImageSize(BaseItem item, ImageType imageType, int imageIndex, ImageDimensions originalImageSize);
EnhancedImageInfo GetEnhancedImageInfo(BaseItem item, string inputFile, ImageType imageType, int imageIndex);
/// <summary>
/// Enhances the image async.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="inputFile">The input file.</param>
/// <param name="outputFile">The output file.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{Image}.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
Task EnhanceImageAsync(BaseItem item, string inputFile, string outputFile, ImageType imageType, int imageIndex);
}
public class EnhancedImageInfo
{
public bool RequiresTransparency { get; set; }
}
}

View File

@@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// Interface IImageProvider
/// Interface IImageProvider.
/// </summary>
public interface IImageProvider
{
@@ -14,10 +14,10 @@ namespace MediaBrowser.Controller.Providers
string Name { get; }
/// <summary>
/// Supportses the specified item.
/// Supports the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
/// <returns><c>true</c> if the provider supports the item.</returns>
bool Supports(BaseItem item);
}
}

View File

@@ -1,10 +0,0 @@
using System.Collections.Generic;
using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Providers
{
public interface ILocalImageFileProvider : ILocalImageProvider
{
List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService);
}
}

View File

@@ -1,9 +1,13 @@
using System.Collections.Generic;
using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// This is just a marker interface
/// This is just a marker interface.
/// </summary>
public interface ILocalImageProvider : IImageProvider
{
List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService);
}
}

View File

@@ -17,8 +17,9 @@ namespace MediaBrowser.Controller.Providers
/// <param name="info">The information.</param>
/// <param name="directoryService">The directory service.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MetadataResult{`0}}.</returns>
Task<MetadataResult<TItemType>> GetMetadata(ItemInfo info,
/// <returns>Task{MetadataResult{0}}.</returns>
Task<MetadataResult<TItemType>> GetMetadata(
ItemInfo info,
IDirectoryService directoryService,
CancellationToken cancellationToken);
}

View File

@@ -12,8 +12,9 @@ namespace MediaBrowser.Controller.Providers
/// Determines whether this instance can refresh the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if this instance can refresh the specified item; otherwise, <c>false</c>.</returns>
/// <returns><c>true</c> if this instance can refresh the specified item.</returns>
bool CanRefresh(BaseItem item);
bool CanRefreshPrimary(Type type);
/// <summary>

View File

@@ -2,6 +2,5 @@ namespace MediaBrowser.Controller.Providers
{
public interface IPreRefreshProvider : ICustomMetadataProvider
{
}
}

View File

@@ -14,7 +14,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// Interface IProviderManager
/// Interface IProviderManager.
/// </summary>
public interface IProviderManager
{
@@ -159,13 +159,17 @@ namespace MediaBrowser.Controller.Providers
Dictionary<Guid, Guid> GetRefreshQueue();
void OnRefreshStart(BaseItem item);
void OnRefreshProgress(BaseItem item, double progress);
void OnRefreshComplete(BaseItem item);
double? GetRefreshProgress(Guid id);
event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
}

View File

@@ -9,7 +9,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// Interface IImageProvider
/// Interface IImageProvider.
/// </summary>
public interface IRemoteImageProvider : IImageProvider
{

View File

@@ -23,10 +23,15 @@ namespace MediaBrowser.Controller.Providers
}
public Type ItemType { get; set; }
public string Path { get; set; }
public string ContainingFolderPath { get; set; }
public VideoType VideoType { get; set; }
public bool IsInMixedFolder { get; set; }
public bool IsPlaceHolder { get; set; }
}
}

View File

@@ -11,29 +11,37 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the metadata language.
/// </summary>
/// <value>The metadata language.</value>
public string MetadataLanguage { get; set; }
/// <summary>
/// Gets or sets the metadata country code.
/// </summary>
/// <value>The metadata country code.</value>
public string MetadataCountryCode { get; set; }
/// <summary>
/// Gets or sets the provider ids.
/// </summary>
/// <value>The provider ids.</value>
public Dictionary<string, string> ProviderIds { get; set; }
/// <summary>
/// Gets or sets the year.
/// </summary>
/// <value>The year.</value>
public int? Year { get; set; }
public int? IndexNumber { get; set; }
public int? ParentIndexNumber { get; set; }
public DateTime? PremiereDate { get; set; }
public bool IsAutomated { get; set; }
public ItemLookupInfo()

View File

@@ -6,6 +6,7 @@ namespace MediaBrowser.Controller.Providers
public class LocalImageInfo
{
public FileSystemMetadata FileInfo { get; set; }
public ImageType Type { get; set; }
}
}

View File

@@ -1,39 +0,0 @@
namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// Determines when a provider should execute, relative to others
/// </summary>
public enum MetadataProviderPriority
{
// Run this provider at the beginning
/// <summary>
/// The first
/// </summary>
First = 1,
// Run this provider after all first priority providers
/// <summary>
/// The second
/// </summary>
Second = 2,
// Run this provider after all second priority providers
/// <summary>
/// The third
/// </summary>
Third = 3,
/// <summary>
/// The fourth
/// </summary>
Fourth = 4,
Fifth = 5,
// Run this provider last
/// <summary>
/// The last
/// </summary>
Last = 999
}
}

View File

@@ -13,11 +13,13 @@ namespace MediaBrowser.Controller.Providers
public bool ReplaceAllMetadata { get; set; }
public MetadataRefreshMode MetadataRefreshMode { get; set; }
public RemoteSearchResult SearchResult { get; set; }
public string[] RefreshPaths { get; set; }
public bool ForceSave { get; set; }
public bool EnableRemoteContentProbe { get; set; }
public MetadataRefreshOptions(IDirectoryService directoryService)

View File

@@ -8,6 +8,7 @@ namespace MediaBrowser.Controller.Providers
public class MetadataResult<T>
{
public List<LocalImageInfo> Images { get; set; }
public List<UserItemData> UserDataList { get; set; }
public MetadataResult()
@@ -19,10 +20,15 @@ namespace MediaBrowser.Controller.Providers
public List<PersonInfo> People { get; set; }
public bool HasMetadata { get; set; }
public T Item { get; set; }
public string ResultLanguage { get; set; }
public string Provider { get; set; }
public bool QueriedById { get; set; }
public void AddPerson(PersonInfo p)
{
if (People == null)

View File

@@ -2,6 +2,5 @@ namespace MediaBrowser.Controller.Providers
{
public class MovieInfo : ItemLookupInfo
{
}
}

View File

@@ -2,6 +2,5 @@ namespace MediaBrowser.Controller.Providers
{
public class PersonLookupInfo : ItemLookupInfo
{
}
}

View File

@@ -10,14 +10,14 @@ namespace MediaBrowser.Controller.Providers
public Guid ItemId { get; set; }
/// <summary>
/// If set will only search within the given provider
/// Will only search within the given provider when set.
/// </summary>
public string SearchProviderName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [include disabled providers].
/// Gets or sets a value indicating whether disabled providers should be included.
/// </summary>
/// <value><c>true</c> if [include disabled providers]; otherwise, <c>false</c>.</value>
/// <value><c>true</c> if disabled providers should be included.</value>
public bool IncludeDisabledProviders { get; set; }
}
}

View File

@@ -115,6 +115,8 @@ namespace MediaBrowser.Controller.Session
public BaseItem FullNowPlayingItem { get; set; }
public BaseItemDto NowViewingItem { get; set; }
/// <summary>
/// Gets or sets the device id.
/// </summary>
@@ -239,11 +241,6 @@ namespace MediaBrowser.Controller.Session
SessionControllers = controllers.ToArray();
}
public bool ContainsUser(string userId)
{
return ContainsUser(new Guid(userId));
}
public bool ContainsUser(Guid userId)
{
if (UserId.Equals(userId))
@@ -253,7 +250,7 @@ namespace MediaBrowser.Controller.Session
foreach (var additionalUser in AdditionalUsers)
{
if (userId.Equals(userId))
if (additionalUser.UserId.Equals(userId))
{
return true;
}
@@ -308,7 +305,7 @@ namespace MediaBrowser.Controller.Session
var newPositionTicks = positionTicks + ProgressIncrement;
var item = progressInfo.Item;
long? runtimeTicks = item == null ? null : item.RunTimeTicks;
long? runtimeTicks = item?.RunTimeTicks;
// Don't report beyond the runtime
if (runtimeTicks.HasValue && newPositionTicks >= runtimeTicks.Value)

View File

@@ -0,0 +1,135 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Sorting
{
public class AlphanumComparator : IComparer<string?>
{
public static int CompareValues(string? s1, string? s2)
{
if (s1 == null && s2 == null)
{
return 0;
}
else if (s1 == null)
{
return -1;
}
else if (s2 == null)
{
return 1;
}
int len1 = s1.Length;
int len2 = s2.Length;
// Early return for empty strings
if (len1 == 0 && len2 == 0)
{
return 0;
}
else if (len1 == 0)
{
return -1;
}
else if (len2 == 0)
{
return 1;
}
int pos1 = 0;
int pos2 = 0;
do
{
int start1 = pos1;
int start2 = pos2;
bool isNum1 = char.IsDigit(s1[pos1++]);
bool isNum2 = char.IsDigit(s2[pos2++]);
while (pos1 < len1 && char.IsDigit(s1[pos1]) == isNum1)
{
pos1++;
}
while (pos2 < len2 && char.IsDigit(s2[pos2]) == isNum2)
{
pos2++;
}
var span1 = s1.AsSpan(start1, pos1 - start1);
var span2 = s2.AsSpan(start2, pos2 - start2);
if (isNum1 && isNum2)
{
// Trim leading zeros so we can compare the length
// of the strings to find the largest number
span1 = span1.TrimStart('0');
span2 = span2.TrimStart('0');
var span1Len = span1.Length;
var span2Len = span2.Length;
if (span1Len < span2Len)
{
return -1;
}
else if (span1Len > span2Len)
{
return 1;
}
else if (span1Len >= 20) // Number is probably too big for a ulong
{
// Trim all the first digits that are the same
int i = 0;
while (i < span1Len && span1[i] == span2[i])
{
i++;
}
// If there are no more digits it's the same number
if (i == span1Len)
{
continue;
}
// Only need to compare the most significant digit
span1 = span1.Slice(i, 1);
span2 = span2.Slice(i, 1);
}
if (!ulong.TryParse(span1, out var num1)
|| !ulong.TryParse(span2, out var num2))
{
return 0;
}
else if (num1 < num2)
{
return -1;
}
else if (num1 > num2)
{
return 1;
}
}
else
{
int result = span1.CompareTo(span2, StringComparison.InvariantCulture);
if (result != 0)
{
return result;
}
}
} while (pos1 < len1 && pos2 < len2);
return len1 - len2;
}
/// <inheritdoc />
public int Compare(string x, string y)
{
return CompareValues(x, y);
}
}
}

View File

@@ -1,143 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MediaBrowser.Controller.Sorting
{
public static class SortExtensions
{
private static readonly AlphanumComparator _comparer = new AlphanumComparator();
public static IEnumerable<T> OrderByString<T>(this IEnumerable<T> list, Func<T, string> getName)
{
return list.OrderBy(getName, new AlphanumComparator());
return list.OrderBy(getName, _comparer);
}
public static IEnumerable<T> OrderByStringDescending<T>(this IEnumerable<T> list, Func<T, string> getName)
{
return list.OrderByDescending(getName, new AlphanumComparator());
return list.OrderByDescending(getName, _comparer);
}
public static IOrderedEnumerable<T> ThenByString<T>(this IOrderedEnumerable<T> list, Func<T, string> getName)
{
return list.ThenBy(getName, new AlphanumComparator());
return list.ThenBy(getName, _comparer);
}
public static IOrderedEnumerable<T> ThenByStringDescending<T>(this IOrderedEnumerable<T> list, Func<T, string> getName)
{
return list.ThenByDescending(getName, new AlphanumComparator());
}
private class AlphanumComparator : IComparer<string>
{
private enum ChunkType { Alphanumeric, Numeric };
private static bool InChunk(char ch, char otherCh)
{
var type = ChunkType.Alphanumeric;
if (char.IsDigit(otherCh))
{
type = ChunkType.Numeric;
}
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
{
return false;
}
return true;
}
public static int CompareValues(string s1, string s2)
{
if (s1 == null || s2 == null)
{
return 0;
}
int thisMarker = 0, thisNumericChunk = 0;
int thatMarker = 0, thatNumericChunk = 0;
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
{
if (thisMarker >= s1.Length)
{
return -1;
}
else if (thatMarker >= s2.Length)
{
return 1;
}
char thisCh = s1[thisMarker];
char thatCh = s2[thatMarker];
var thisChunk = new StringBuilder();
var thatChunk = new StringBuilder();
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || InChunk(thisCh, thisChunk[0])))
{
thisChunk.Append(thisCh);
thisMarker++;
if (thisMarker < s1.Length)
{
thisCh = s1[thisMarker];
}
}
while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || InChunk(thatCh, thatChunk[0])))
{
thatChunk.Append(thatCh);
thatMarker++;
if (thatMarker < s2.Length)
{
thatCh = s2[thatMarker];
}
}
int result = 0;
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
{
return 0;
}
if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
{
return 0;
}
if (thisNumericChunk < thatNumericChunk)
{
result = -1;
}
if (thisNumericChunk > thatNumericChunk)
{
result = 1;
}
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
public int Compare(string x, string y)
{
return CompareValues(x, y);
}
return list.ThenByDescending(getName, _comparer);
}
}
}

View File

@@ -1,25 +0,0 @@
namespace MediaBrowser.Controller.Sorting
{
public static class SortHelper
{
private enum ChunkType { Alphanumeric, Numeric };
public static bool InChunk(char ch, char otherCh)
{
var type = ChunkType.Alphanumeric;
if (char.IsDigit(otherCh))
{
type = ChunkType.Numeric;
}
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
{
return false;
}
return true;
}
}
}