Merge branch 'master' into minor

This commit is contained in:
Bond_009
2020-04-01 18:53:22 +02:00
737 changed files with 3999 additions and 3436 deletions

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

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

View File

@@ -33,7 +33,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 +178,7 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public int? TotalBitrate { get; set; }
[JsonIgnore]
public ExtraType? ExtraType { get; set; }
@@ -387,15 +388,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 +404,6 @@ namespace MediaBrowser.Controller.Entities
}
}
var isNumeric = thisChunk.Length > 0 && char.IsDigit(thisChunk[0]);
list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric));
}
@@ -2194,13 +2191,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 +2224,6 @@ namespace MediaBrowser.Controller.Entities
existingImage.Width = image.Width;
existingImage.Height = image.Height;
}
else
{
var current = ImageInfos;
@@ -2274,7 +2266,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 +2303,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 +2597,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 +2657,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;
@@ -2887,7 +2885,7 @@ namespace MediaBrowser.Controller.Entities
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?.ExtraType != null && extraTypes.Contains(i.ExtraType.Value));
}
public IEnumerable<BaseItem> GetTrailers()
@@ -2904,11 +2902,17 @@ namespace MediaBrowser.Controller.Entities
}
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;
@@ -2918,5 +2922,17 @@ namespace MediaBrowser.Controller.Entities
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 };
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

@@ -324,10 +324,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();
@@ -393,7 +393,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);
});
@@ -423,7 +423,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)
{
@@ -808,11 +808,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);
@@ -824,7 +858,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.ToArray();
}
return GetItemsInternal(query).Items;

View File

@@ -13,24 +13,37 @@ namespace MediaBrowser.Controller.Extensions
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 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

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

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

@@ -8,7 +8,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.3" />
</ItemGroup>
<ItemGroup>

View File

@@ -460,16 +460,7 @@ 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(' ');
@@ -480,20 +471,26 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions);
var outputVideoCodec = GetVideoEncoder(state, encodingOptions);
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
if (encodingOptions.EnableHardwareEncoding && outputVideoCodec.Contains("qsv", StringComparison.OrdinalIgnoreCase))
if (!hasTextSubs)
{
if (!string.IsNullOrEmpty(videoDecoder) && videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase))
// While using QSV encoder
if ((outputVideoCodec ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1)
{
arg.Append("-hwaccel qsv ");
}
else
{
arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
// 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(videoDecoder + " ");
}
arg.Append("-i ")
@@ -503,17 +500,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))
@@ -1546,9 +1532,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 +1551,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 (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,12 +1632,34 @@ 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(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
{
/*
[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 (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding)
{
/*
[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.
@@ -1688,7 +1723,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 +1743,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)
{
@@ -1737,7 +1775,7 @@ namespace MediaBrowser.Controller.MediaEncoding
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 +1979,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 +1988,56 @@ 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;
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
// When the input may or may not be hardware VAAPI decodable
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
{
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(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
{
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 (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding)
{
var codec = videoStream.Codec.ToLowerInvariant();
var pixelFormat = videoStream.PixelFormat.ToLowerInvariant();
// Assert 10-bit hardware VAAPI decodable
if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
&& (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
{
filters.Add("hwdownload");
filters.Add("format=p010le");
filters.Add("format=nv12");
}
// Assert 8-bit hardware VAAPI decodable
else if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) == -1)
{
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 +2046,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 +2072,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(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
{
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 +2104,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
filters.Add("hwmap");
}
if (allowTimeStampCopy)
{
output += " -copyts";
}
}
if (filters.Count > 0)
@@ -2218,7 +2280,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 +2289,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)
{
@@ -2525,6 +2587,12 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
// 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;
@@ -2772,14 +2840,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)
@@ -2787,13 +2868,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);
@@ -2899,6 +2979,5 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Empty,
string.Empty).Trim();
}
}
}

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

@@ -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,7 +64,6 @@ namespace MediaBrowser.Controller.Providers
}
return file;
//return _fileSystem.GetFileInfo(path);
}
public IReadOnlyList<string> GetFilePaths(string path)

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);
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

@@ -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

@@ -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

@@ -7,137 +7,25 @@ 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;
}
}
}