mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Reduce some allocations
This commit is contained in:
parent
5acb4e9491
commit
1b49435a0e
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.Audio
|
namespace Emby.Naming.Audio
|
||||||
{
|
{
|
||||||
@ -18,8 +18,8 @@ namespace Emby.Naming.Audio
|
|||||||
/// <returns>True if file at path is audio file.</returns>
|
/// <returns>True if file at path is audio file.</returns>
|
||||||
public static bool IsAudioFile(string path, NamingOptions options)
|
public static bool IsAudioFile(string path, NamingOptions options)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
return options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -29,36 +29,33 @@ namespace Emby.Naming.Video
|
|||||||
/// <param name="path">Path to file.</param>
|
/// <param name="path">Path to file.</param>
|
||||||
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
||||||
public ExtraResult GetExtraInfo(string path)
|
public ExtraResult GetExtraInfo(string path)
|
||||||
{
|
|
||||||
return _options.VideoExtraRules
|
|
||||||
.Select(i => GetExtraInfo(path, i))
|
|
||||||
.FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExtraResult GetExtraInfo(string path, ExtraRule rule)
|
|
||||||
{
|
{
|
||||||
var result = new ExtraResult();
|
var result = new ExtraResult();
|
||||||
|
|
||||||
|
for (var i = 0; i < _options.VideoExtraRules.Length; i++)
|
||||||
|
{
|
||||||
|
var rule = _options.VideoExtraRules[i];
|
||||||
if (rule.MediaType == MediaType.Audio)
|
if (rule.MediaType == MediaType.Audio)
|
||||||
{
|
{
|
||||||
if (!AudioFileParser.IsAudioFile(path, _options))
|
if (!AudioFileParser.IsAudioFile(path, _options))
|
||||||
{
|
{
|
||||||
return result;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (rule.MediaType == MediaType.Video)
|
else if (rule.MediaType == MediaType.Video)
|
||||||
{
|
{
|
||||||
if (!new VideoResolver(_options).IsVideoFile(path))
|
if (!new VideoResolver(_options).IsVideoFile(path))
|
||||||
{
|
{
|
||||||
return result;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pathSpan = path.AsSpan();
|
||||||
if (rule.RuleType == ExtraRuleType.Filename)
|
if (rule.RuleType == ExtraRuleType.Filename)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileNameWithoutExtension(path);
|
var filename = Path.GetFileNameWithoutExtension(pathSpan);
|
||||||
|
|
||||||
if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase))
|
if (filename.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.ExtraType = rule.ExtraType;
|
result.ExtraType = rule.ExtraType;
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
@ -66,9 +63,9 @@ namespace Emby.Naming.Video
|
|||||||
}
|
}
|
||||||
else if (rule.RuleType == ExtraRuleType.Suffix)
|
else if (rule.RuleType == ExtraRuleType.Suffix)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileNameWithoutExtension(path);
|
var filename = Path.GetFileNameWithoutExtension(pathSpan);
|
||||||
|
|
||||||
if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0)
|
if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.ExtraType = rule.ExtraType;
|
result.ExtraType = rule.ExtraType;
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
@ -88,14 +85,20 @@ namespace Emby.Naming.Video
|
|||||||
}
|
}
|
||||||
else if (rule.RuleType == ExtraRuleType.DirectoryName)
|
else if (rule.RuleType == ExtraRuleType.DirectoryName)
|
||||||
{
|
{
|
||||||
var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
|
var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan));
|
||||||
if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
|
if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.ExtraType = rule.ExtraType;
|
result.ExtraType = rule.ExtraType;
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.ExtraType != null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
@ -59,15 +59,15 @@ namespace Emby.Naming.Video
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isStub = false;
|
bool isStub = false;
|
||||||
string? container = null;
|
ReadOnlySpan<char> container = null;
|
||||||
string? stubType = null;
|
string? stubType = null;
|
||||||
|
|
||||||
if (!isDirectory)
|
if (!isDirectory)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
|
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// It's not supported. Check stub extensions
|
// It's not supported. Check stub extensions
|
||||||
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
||||||
@ -86,9 +86,7 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
|
var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
|
||||||
|
|
||||||
var name = isDirectory
|
var name = Path.GetFileNameWithoutExtension(path);
|
||||||
? Path.GetFileName(path)
|
|
||||||
: Path.GetFileNameWithoutExtension(path);
|
|
||||||
|
|
||||||
int? year = null;
|
int? year = null;
|
||||||
|
|
||||||
@ -107,7 +105,7 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
return new VideoFileInfo(
|
return new VideoFileInfo(
|
||||||
path: path,
|
path: path,
|
||||||
container: container,
|
container: container.ToString(),
|
||||||
isStub: isStub,
|
isStub: isStub,
|
||||||
name: name,
|
name: name,
|
||||||
year: year,
|
year: year,
|
||||||
@ -126,8 +124,8 @@ namespace Emby.Naming.Video
|
|||||||
/// <returns>True if is video file.</returns>
|
/// <returns>True if is video file.</returns>
|
||||||
public bool IsVideoFile(string path)
|
public bool IsVideoFile(string path)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -137,8 +135,8 @@ namespace Emby.Naming.Video
|
|||||||
/// <returns>True if is video file stub.</returns>
|
/// <returns>True if is video file stub.</returns>
|
||||||
public bool IsStubFile(string path)
|
public bool IsStubFile(string path)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -181,11 +181,9 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
|
foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
|
||||||
{
|
{
|
||||||
if (row[1].SQLiteType != SQLiteType.Null)
|
if (row.TryGetString(1, out var columnName))
|
||||||
{
|
{
|
||||||
var name = row[1].ToString();
|
columnNames.Add(columnName);
|
||||||
|
|
||||||
columnNames.Add(name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using SQLitePCL.pretty;
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
@ -96,21 +97,42 @@ namespace Emby.Server.Implementations.Data
|
|||||||
DateTimeStyles.None).ToUniversalTime();
|
DateTimeStyles.None).ToUniversalTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DateTime? TryReadDateTime(this IResultSetValue result)
|
public static bool TryReadDateTime(this IReadOnlyList<IResultSetValue> reader, int index, [NotNullWhen(true)] out DateTime? result)
|
||||||
{
|
{
|
||||||
var dateText = result.ToString();
|
result = null;
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dateText = item.ToString();
|
||||||
|
|
||||||
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult))
|
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult))
|
||||||
{
|
{
|
||||||
return dateTimeResult.ToUniversalTime();
|
result = dateTimeResult.ToUniversalTime();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsDBNull(this IReadOnlyList<IResultSetValue> result, int index)
|
public static bool TryGetGuid(this IReadOnlyList<IResultSetValue> reader, int index, [NotNullWhen(true)] out Guid? result)
|
||||||
{
|
{
|
||||||
return result[index].SQLiteType == SQLiteType.Null;
|
result = null;
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ReadGuidFromBlob();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsDbNull(this IResultSetValue result)
|
||||||
|
{
|
||||||
|
return result.SQLiteType == SQLiteType.Null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetString(this IReadOnlyList<IResultSetValue> result, int index)
|
public static string GetString(this IReadOnlyList<IResultSetValue> result, int index)
|
||||||
@ -118,14 +140,48 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return result[index].ToString();
|
return result[index].ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryGetString(this IReadOnlyList<IResultSetValue> reader, int index, out string result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToString();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index)
|
public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index)
|
||||||
{
|
{
|
||||||
return result[index].ToBool();
|
return result[index].ToBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetInt32(this IReadOnlyList<IResultSetValue> result, int index)
|
public static bool TryGetBoolean(this IReadOnlyList<IResultSetValue> reader, int index, [NotNullWhen(true)] out bool? result)
|
||||||
{
|
{
|
||||||
return result[index].ToInt();
|
result = null;
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToBool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetInt(this IReadOnlyList<IResultSetValue> reader, int index, out int? result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToInt();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index)
|
public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index)
|
||||||
@ -133,9 +189,43 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return result[index].ToInt64();
|
return result[index].ToInt64();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float GetFloat(this IReadOnlyList<IResultSetValue> result, int index)
|
public static bool TryGetLong(this IReadOnlyList<IResultSetValue> reader, int index, out long? result)
|
||||||
{
|
{
|
||||||
return result[index].ToFloat();
|
result = null;
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToInt64();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetFloat(this IReadOnlyList<IResultSetValue> reader, int index, out float? result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToFloat();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetDouble(this IReadOnlyList<IResultSetValue> reader, int index, out double? result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToDouble();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index)
|
public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -355,9 +355,9 @@ namespace Emby.Server.Implementations.Data
|
|||||||
userData.Key = reader[0].ToString();
|
userData.Key = reader[0].ToString();
|
||||||
// userData.UserId = reader[1].ReadGuidFromBlob();
|
// userData.UserId = reader[1].ReadGuidFromBlob();
|
||||||
|
|
||||||
if (reader[2].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetDouble(2, out var rating))
|
||||||
{
|
{
|
||||||
userData.Rating = reader[2].ToDouble();
|
userData.Rating = rating;
|
||||||
}
|
}
|
||||||
|
|
||||||
userData.Played = reader[3].ToBool();
|
userData.Played = reader[3].ToBool();
|
||||||
@ -365,19 +365,19 @@ namespace Emby.Server.Implementations.Data
|
|||||||
userData.IsFavorite = reader[5].ToBool();
|
userData.IsFavorite = reader[5].ToBool();
|
||||||
userData.PlaybackPositionTicks = reader[6].ToInt64();
|
userData.PlaybackPositionTicks = reader[6].ToInt64();
|
||||||
|
|
||||||
if (reader[7].SQLiteType != SQLiteType.Null)
|
if (reader.TryReadDateTime(7, out var lastPlayedDate))
|
||||||
{
|
{
|
||||||
userData.LastPlayedDate = reader[7].TryReadDateTime();
|
userData.LastPlayedDate = lastPlayedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader[8].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetInt(8, out var audioStreamIndex))
|
||||||
{
|
{
|
||||||
userData.AudioStreamIndex = reader[8].ToInt();
|
userData.AudioStreamIndex = audioStreamIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader[9].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetInt(9, out var subtitleStreamIndex))
|
||||||
{
|
{
|
||||||
userData.SubtitleStreamIndex = reader[9].ToInt();
|
userData.SubtitleStreamIndex = subtitleStreamIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
return userData;
|
return userData;
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
auth = httpReq.Request.Headers[HeaderNames.Authorization];
|
auth = httpReq.Request.Headers[HeaderNames.Authorization];
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetAuthorization(auth);
|
return GetAuthorization(auth.Count > 0 ? auth[0] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -232,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
auth = httpReq.Headers[HeaderNames.Authorization];
|
auth = httpReq.Headers[HeaderNames.Authorization];
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetAuthorization(auth);
|
return GetAuthorization(auth.Count > 0 ? auth[0] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -240,43 +240,43 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="authorizationHeader">The authorization header.</param>
|
/// <param name="authorizationHeader">The authorization header.</param>
|
||||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||||
private Dictionary<string, string> GetAuthorization(string authorizationHeader)
|
private Dictionary<string, string> GetAuthorization(ReadOnlySpan<char> authorizationHeader)
|
||||||
{
|
{
|
||||||
if (authorizationHeader == null)
|
if (authorizationHeader == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts = authorizationHeader.Split(' ', 2);
|
var firstSpace = authorizationHeader.IndexOf(' ');
|
||||||
|
|
||||||
// There should be at least to parts
|
// There should be at least two parts
|
||||||
if (parts.Length != 2)
|
if (firstSpace == -1)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var acceptedNames = new[] { "MediaBrowser", "Emby" };
|
var name = authorizationHeader[..firstSpace];
|
||||||
|
|
||||||
// It has to be a digest request
|
if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase)
|
||||||
if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase))
|
&& name.Equals("Emby", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove uptil the first space
|
authorizationHeader = authorizationHeader[(firstSpace + 1)..];
|
||||||
authorizationHeader = parts[1];
|
|
||||||
parts = authorizationHeader.Split(',');
|
|
||||||
|
|
||||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (var item in parts)
|
foreach (var item in authorizationHeader.Split(','))
|
||||||
{
|
{
|
||||||
var param = item.Trim().Split('=', 2);
|
var trimmedItem = item.Trim();
|
||||||
|
var firstEqualsSign = trimmedItem.IndexOf('=');
|
||||||
|
|
||||||
if (param.Length == 2)
|
if (firstEqualsSign > 0)
|
||||||
{
|
{
|
||||||
var value = NormalizeValue(param[1].Trim('"'));
|
var key = trimmedItem[..firstEqualsSign].ToString();
|
||||||
result[param[0]] = value;
|
var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString());
|
||||||
|
result[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,13 +165,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
|
|
||||||
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
|
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(video.Path);
|
var extension = Path.GetExtension(video.Path.AsSpan());
|
||||||
video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) ||
|
video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase)
|
||||||
string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ?
|
|| extension.Equals(".img", StringComparison.OrdinalIgnoreCase)
|
||||||
VideoType.Iso :
|
? VideoType.Iso
|
||||||
VideoType.VideoFile;
|
: VideoType.VideoFile;
|
||||||
|
|
||||||
video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase);
|
video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase);
|
||||||
video.IsPlaceHolder = videoInfo.IsStub;
|
video.IsPlaceHolder = videoInfo.IsStub;
|
||||||
|
|
||||||
if (videoInfo.IsStub)
|
if (videoInfo.IsStub)
|
||||||
@ -193,11 +193,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
{
|
{
|
||||||
if (video.VideoType == VideoType.Iso)
|
if (video.VideoType == VideoType.Iso)
|
||||||
{
|
{
|
||||||
if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1)
|
if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
video.IsoType = IsoType.Dvd;
|
video.IsoType = IsoType.Dvd;
|
||||||
}
|
}
|
||||||
else if (video.Path.IndexOf("bluray", StringComparison.OrdinalIgnoreCase) != -1)
|
else if (video.Path.Contains("bluray", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
video.IsoType = IsoType.BluRay;
|
video.IsoType = IsoType.BluRay;
|
||||||
}
|
}
|
||||||
|
@ -297,50 +297,49 @@ namespace Emby.Server.Implementations.Security
|
|||||||
AccessToken = reader[1].ToString()
|
AccessToken = reader[1].ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (reader[2].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetString(2, out var deviceId))
|
||||||
{
|
{
|
||||||
info.DeviceId = reader[2].ToString();
|
info.DeviceId = deviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader[3].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetString(3, out var appName))
|
||||||
{
|
{
|
||||||
info.AppName = reader[3].ToString();
|
info.AppName = appName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader[4].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetString(4, out var appVersion))
|
||||||
{
|
{
|
||||||
info.AppVersion = reader[4].ToString();
|
info.AppVersion = appVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader[5].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetString(6, out var userId))
|
||||||
{
|
{
|
||||||
info.DeviceName = reader[5].ToString();
|
info.UserId = new Guid(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader[6].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetString(7, out var userName))
|
||||||
{
|
{
|
||||||
info.UserId = new Guid(reader[6].ToString());
|
info.UserName = userName;
|
||||||
}
|
|
||||||
|
|
||||||
if (reader[7].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.UserName = reader[7].ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info.DateCreated = reader[8].ReadDateTime();
|
info.DateCreated = reader[8].ReadDateTime();
|
||||||
|
|
||||||
if (reader[9].SQLiteType != SQLiteType.Null)
|
if (reader.TryReadDateTime(9, out var dateLastActivity))
|
||||||
{
|
{
|
||||||
info.DateLastActivity = reader[9].ReadDateTime();
|
info.DateLastActivity = dateLastActivity.Value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
info.DateLastActivity = info.DateCreated;
|
info.DateLastActivity = info.DateCreated;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader[10].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetString(10, out var customName))
|
||||||
{
|
{
|
||||||
info.DeviceName = reader[10].ToString();
|
info.DeviceName = customName;
|
||||||
|
}
|
||||||
|
else if (reader.TryGetString(5, out var deviceName))
|
||||||
|
{
|
||||||
|
info.DeviceName = deviceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
@ -361,9 +360,9 @@ namespace Emby.Server.Implementations.Security
|
|||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
foreach (var row in statement.ExecuteQuery())
|
||||||
{
|
{
|
||||||
if (row[0].SQLiteType != SQLiteType.Null)
|
if (row.TryGetString(0, out var customName))
|
||||||
{
|
{
|
||||||
result.CustomName = row[0].ToString();
|
result.CustomName = customName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
Jellyfin.sln
11
Jellyfin.sln
@ -81,6 +81,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "te
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -223,6 +225,14 @@ Global
|
|||||||
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{50928738-D268-43A3-BACA-3513D9AA6B8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{50928738-D268-43A3-BACA-3513D9AA6B8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{50928738-D268-43A3-BACA-3513D9AA6B8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{50928738-D268-43A3-BACA-3513D9AA6B8E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -240,6 +250,7 @@ Global
|
|||||||
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
||||||
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
||||||
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
||||||
|
{A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
|
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
|
||||||
|
51
MediaBrowser.Common/Extensions/EnumerableExtensions.cs
Normal file
51
MediaBrowser.Common/Extensions/EnumerableExtensions.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Extensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Static extensions for the <see cref="IEnumerable{T}"/> interface.
|
||||||
|
/// </summary>
|
||||||
|
public static class EnumerableExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the value is contained in the source collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">An instance of the <see cref="IEnumerable{String}"/> interface.</param>
|
||||||
|
/// <param name="value">The value to look for in the collection.</param>
|
||||||
|
/// <param name="stringComparison">The string comparison.</param>
|
||||||
|
/// <returns>A value indicating whether the value is contained in the collection.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">The source is null.</exception>
|
||||||
|
public static bool Contains(this IEnumerable<string> source, ReadOnlySpan<char> value, StringComparison stringComparison)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source is IList<string> list)
|
||||||
|
{
|
||||||
|
int len = list.Count;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
if (value.Equals(list[i], stringComparison))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string element in source)
|
||||||
|
{
|
||||||
|
if (value.Equals(element, stringComparison))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -466,7 +466,7 @@ namespace MediaBrowser.LocalMetadata.Images
|
|||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool AddImage(IEnumerable<FileSystemMetadata> files, List<LocalImageInfo> images, string name, ImageType type)
|
private bool AddImage(List<FileSystemMetadata> files, List<LocalImageInfo> images, string name, ImageType type)
|
||||||
{
|
{
|
||||||
var image = GetImage(files, name);
|
var image = GetImage(files, name);
|
||||||
|
|
||||||
@ -484,9 +484,20 @@ namespace MediaBrowser.LocalMetadata.Images
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileSystemMetadata? GetImage(IEnumerable<FileSystemMetadata> files, string name)
|
private static FileSystemMetadata? GetImage(IReadOnlyList<FileSystemMetadata> files, string name)
|
||||||
{
|
{
|
||||||
return files.FirstOrDefault(i => !i.IsDirectory && string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase) && i.Length > 0);
|
for (var i = 0; i < files.Count; i++)
|
||||||
|
{
|
||||||
|
var file = files[i];
|
||||||
|
if (!file.IsDirectory
|
||||||
|
&& file.Length > 0
|
||||||
|
&& Path.GetFileNameWithoutExtension(file.FullName.AsSpan()).Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,17 +15,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
{
|
{
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
|
||||||
private static readonly HashSet<string> SubtitleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
||||||
{
|
|
||||||
".srt",
|
|
||||||
".ssa",
|
|
||||||
".ass",
|
|
||||||
".sub",
|
|
||||||
".smi",
|
|
||||||
".sami",
|
|
||||||
".vtt"
|
|
||||||
};
|
|
||||||
|
|
||||||
public SubtitleResolver(ILocalizationManager localization)
|
public SubtitleResolver(ILocalizationManager localization)
|
||||||
{
|
{
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
@ -88,6 +77,115 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddExternalSubtitleStreams(
|
||||||
|
List<MediaStream> streams,
|
||||||
|
string videoPath,
|
||||||
|
int startIndex,
|
||||||
|
string[] files)
|
||||||
|
{
|
||||||
|
var videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoPath);
|
||||||
|
|
||||||
|
foreach (var fullName in files)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(fullName.AsSpan());
|
||||||
|
if (!IsSubtitleExtension(extension))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fullName);
|
||||||
|
|
||||||
|
MediaStream mediaStream;
|
||||||
|
|
||||||
|
// The subtitle filename must either be equal to the video filename or start with the video filename followed by a dot
|
||||||
|
if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mediaStream = new MediaStream
|
||||||
|
{
|
||||||
|
Index = startIndex++,
|
||||||
|
Type = MediaStreamType.Subtitle,
|
||||||
|
IsExternal = true,
|
||||||
|
Path = fullName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (fileNameWithoutExtension.Length >= videoFileNameWithoutExtension.Length
|
||||||
|
&& fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.'
|
||||||
|
&& fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var isForced = fullName.Contains(".forced.", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| fullName.Contains(".foreign.", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var isDefault = fullName.Contains(".default.", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Support xbmc naming conventions - 300.spanish.srt
|
||||||
|
var languageSpan = fileNameWithoutExtension;
|
||||||
|
while (languageSpan.Length > 0)
|
||||||
|
{
|
||||||
|
var lastDot = languageSpan.LastIndexOf('.');
|
||||||
|
var currentSlice = languageSpan[lastDot..];
|
||||||
|
if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
languageSpan = languageSpan[..lastDot];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
languageSpan = languageSpan[(lastDot + 1)..];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var language = languageSpan.ToString();
|
||||||
|
// Try to translate to three character code
|
||||||
|
// Be flexible and check against both the full and three character versions
|
||||||
|
var culture = _localization.FindLanguageInfo(language);
|
||||||
|
|
||||||
|
if (culture != null)
|
||||||
|
{
|
||||||
|
language = culture.ThreeLetterISOLanguageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaStream = new MediaStream
|
||||||
|
{
|
||||||
|
Index = startIndex++,
|
||||||
|
Type = MediaStreamType.Subtitle,
|
||||||
|
IsExternal = true,
|
||||||
|
Path = fullName,
|
||||||
|
Language = language,
|
||||||
|
IsForced = isForced,
|
||||||
|
IsDefault = isDefault
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant();
|
||||||
|
|
||||||
|
streams.Add(mediaStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSubtitleExtension(ReadOnlySpan<char> extension)
|
||||||
|
{
|
||||||
|
return extension.Equals(".srt", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals(".ssa", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals(".ass", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals(".sub", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals(".vtt", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals(".smi", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals(".sami", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ReadOnlySpan<char> NormalizeFilenameForSubtitleComparison(string filename)
|
||||||
|
{
|
||||||
|
// Try to account for sloppy file naming
|
||||||
|
filename = filename.Replace("_", string.Empty, StringComparison.Ordinal);
|
||||||
|
filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal);
|
||||||
|
return Path.GetFileNameWithoutExtension(filename.AsSpan());
|
||||||
|
}
|
||||||
|
|
||||||
private void AddExternalSubtitleStreams(
|
private void AddExternalSubtitleStreams(
|
||||||
List<MediaStream> streams,
|
List<MediaStream> streams,
|
||||||
string folder,
|
string folder,
|
||||||
@ -100,104 +198,5 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
|
|
||||||
AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
|
AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddExternalSubtitleStreams(
|
|
||||||
List<MediaStream> streams,
|
|
||||||
string videoPath,
|
|
||||||
int startIndex,
|
|
||||||
string[] files)
|
|
||||||
{
|
|
||||||
var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoPath);
|
|
||||||
videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoFileNameWithoutExtension);
|
|
||||||
|
|
||||||
foreach (var fullName in files)
|
|
||||||
{
|
|
||||||
var extension = Path.GetExtension(fullName);
|
|
||||||
|
|
||||||
if (!SubtitleExtensions.Contains(extension))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
|
|
||||||
fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fileNameWithoutExtension);
|
|
||||||
|
|
||||||
if (!string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var codec = Path.GetExtension(fullName).ToLowerInvariant().TrimStart('.');
|
|
||||||
|
|
||||||
if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
codec = "srt";
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the subtitle file matches the video file name
|
|
||||||
if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
streams.Add(new MediaStream
|
|
||||||
{
|
|
||||||
Index = startIndex++,
|
|
||||||
Type = MediaStreamType.Subtitle,
|
|
||||||
IsExternal = true,
|
|
||||||
Path = fullName,
|
|
||||||
Codec = codec
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
var isForced = fullName.IndexOf(".forced.", StringComparison.OrdinalIgnoreCase) != -1 ||
|
|
||||||
fullName.IndexOf(".foreign.", StringComparison.OrdinalIgnoreCase) != -1;
|
|
||||||
|
|
||||||
var isDefault = fullName.IndexOf(".default.", StringComparison.OrdinalIgnoreCase) != -1;
|
|
||||||
|
|
||||||
// Support xbmc naming conventions - 300.spanish.srt
|
|
||||||
var language = fileNameWithoutExtension
|
|
||||||
.Replace(".forced", string.Empty, StringComparison.OrdinalIgnoreCase)
|
|
||||||
.Replace(".foreign", string.Empty, StringComparison.OrdinalIgnoreCase)
|
|
||||||
.Replace(".default", string.Empty, StringComparison.OrdinalIgnoreCase)
|
|
||||||
.Split('.')
|
|
||||||
.LastOrDefault();
|
|
||||||
|
|
||||||
// Try to translate to three character code
|
|
||||||
// Be flexible and check against both the full and three character versions
|
|
||||||
var culture = _localization.FindLanguageInfo(language);
|
|
||||||
|
|
||||||
if (culture != null)
|
|
||||||
{
|
|
||||||
language = culture.ThreeLetterISOLanguageName;
|
|
||||||
}
|
|
||||||
|
|
||||||
streams.Add(new MediaStream
|
|
||||||
{
|
|
||||||
Index = startIndex++,
|
|
||||||
Type = MediaStreamType.Subtitle,
|
|
||||||
IsExternal = true,
|
|
||||||
Path = fullName,
|
|
||||||
Codec = codec,
|
|
||||||
Language = language,
|
|
||||||
IsForced = isForced,
|
|
||||||
IsDefault = isDefault
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string NormalizeFilenameForSubtitleComparison(string filename)
|
|
||||||
{
|
|
||||||
// Try to account for sloppy file naming
|
|
||||||
filename = filename.Replace("_", string.Empty, StringComparison.Ordinal);
|
|
||||||
filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal);
|
|
||||||
|
|
||||||
// can't normalize this due to languages such as pt-br
|
|
||||||
// filename = filename.Replace("-", string.Empty);
|
|
||||||
|
|
||||||
// filename = filename.Replace(".", string.Empty);
|
|
||||||
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||||
|
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||||
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Code Analyzers -->
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../../MediaBrowser.Providers\MediaBrowser.Providers.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -0,0 +1,96 @@
|
|||||||
|
#pragma warning disable CA1002 // Do not expose generic lists
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Globalization;
|
||||||
|
using MediaBrowser.Providers.MediaInfo;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Jellyfin.Providers.Tests.MediaInfo
|
||||||
|
{
|
||||||
|
public class SubtitleResolverTests
|
||||||
|
{
|
||||||
|
public static IEnumerable<object[]> AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData()
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new List<MediaStream>(),
|
||||||
|
"/video/My.Video.mkv",
|
||||||
|
index,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"/video/My.Video.mp3",
|
||||||
|
"/video/My.Video.png",
|
||||||
|
"/video/My.Video.srt",
|
||||||
|
"/video/My.Video.txt",
|
||||||
|
"/video/My.Video.vtt",
|
||||||
|
"/video/My.Video.ass",
|
||||||
|
"/video/My.Video.sub",
|
||||||
|
"/video/My.Video.ssa",
|
||||||
|
"/video/My.Video.smi",
|
||||||
|
"/video/My.Video.sami",
|
||||||
|
"/video/My.Video.en.srt",
|
||||||
|
"/video/My.Video.default.en.srt",
|
||||||
|
"/video/My.Video.default.forced.en.srt",
|
||||||
|
"/video/My.Video.en.default.forced.srt",
|
||||||
|
"/video/My.Video.With.Additional.Garbage.en.srt",
|
||||||
|
"/video/My.Video With Additional Garbage.srt"
|
||||||
|
},
|
||||||
|
new List<MediaStream>
|
||||||
|
{
|
||||||
|
CreateMediaStream("/video/My.Video.srt", "srt", null, index++),
|
||||||
|
CreateMediaStream("/video/My.Video.vtt", "vtt", null, index++),
|
||||||
|
CreateMediaStream("/video/My.Video.ass", "ass", null, index++),
|
||||||
|
CreateMediaStream("/video/My.Video.sub", "sub", null, index++),
|
||||||
|
CreateMediaStream("/video/My.Video.ssa", "ssa", null, index++),
|
||||||
|
CreateMediaStream("/video/My.Video.smi", "smi", null, index++),
|
||||||
|
CreateMediaStream("/video/My.Video.sami", "sami", null, index++),
|
||||||
|
CreateMediaStream("/video/My.Video.en.srt", "srt", "en", index++),
|
||||||
|
CreateMediaStream("/video/My.Video.default.en.srt", "srt", "en", index++, isDefault: true),
|
||||||
|
CreateMediaStream("/video/My.Video.default.forced.en.srt", "srt", "en", index++, isForced: true, isDefault: true),
|
||||||
|
CreateMediaStream("/video/My.Video.en.default.forced.srt", "srt", "en", index++, isForced: true, isDefault: true),
|
||||||
|
CreateMediaStream("/video/My.Video.With.Additional.Garbage.en.srt", "srt", "en", index),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData))]
|
||||||
|
public void AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles(List<MediaStream> streams, string videoPath, int startIndex, string[] files, List<MediaStream> expectedResult)
|
||||||
|
{
|
||||||
|
new SubtitleResolver(Mock.Of<ILocalizationManager>()).AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
|
||||||
|
|
||||||
|
Assert.Equal(expectedResult.Count, streams.Count);
|
||||||
|
for (var i = 0; i < expectedResult.Count; i++)
|
||||||
|
{
|
||||||
|
var expected = expectedResult[i];
|
||||||
|
var actual = streams[i];
|
||||||
|
|
||||||
|
Assert.Equal(expected.Index, actual.Index);
|
||||||
|
Assert.Equal(expected.Type, actual.Type);
|
||||||
|
Assert.Equal(expected.IsExternal, actual.IsExternal);
|
||||||
|
Assert.Equal(expected.Path, actual.Path);
|
||||||
|
Assert.Equal(expected.IsDefault, actual.IsDefault);
|
||||||
|
Assert.Equal(expected.IsForced, actual.IsForced);
|
||||||
|
Assert.Equal(expected.Language, actual.Language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MediaStream CreateMediaStream(string path, string codec, string? language, int index, bool isForced = false, bool isDefault = false)
|
||||||
|
{
|
||||||
|
return new ()
|
||||||
|
{
|
||||||
|
Index = index,
|
||||||
|
Codec = codec,
|
||||||
|
Type = MediaStreamType.Subtitle,
|
||||||
|
IsExternal = true,
|
||||||
|
Path = path,
|
||||||
|
IsDefault = isDefault,
|
||||||
|
IsForced = isForced,
|
||||||
|
Language = language
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user