diff options
43 files changed, 438 insertions, 788 deletions
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index 85cecdc44..b7090b262 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -17,4 +17,16 @@ <Compile Include="..\SharedVersion.cs" /> </ItemGroup> + <!-- Code analysers--> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> + <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + </Project> diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 4e0f9493a..eca4b56eb 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; @@ -11,7 +10,6 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -23,7 +21,7 @@ namespace Emby.Drawing /// <summary> /// Class ImageProcessor. /// </summary> - public class ImageProcessor : IImageProcessor, IDisposable + public sealed class ImageProcessor : IImageProcessor, IDisposable { // Increment this when there's a change requiring caches to be invalidated private const string Version = "3"; @@ -31,28 +29,24 @@ namespace Emby.Drawing private static readonly HashSet<string> _transparentImageTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; - /// <summary> - /// The _logger - /// </summary> private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IServerApplicationPaths _appPaths; - private IImageEncoder _imageEncoder; + private readonly IImageEncoder _imageEncoder; private readonly Func<ILibraryManager> _libraryManager; private readonly Func<IMediaEncoder> _mediaEncoder; - private readonly Dictionary<string, LockInfo> _locks = new Dictionary<string, LockInfo>(); private bool _disposed = false; /// <summary> - /// + /// Initializes a new instance of the <see cref="ImageProcessor"/> class. /// </summary> - /// <param name="logger"></param> - /// <param name="appPaths"></param> - /// <param name="fileSystem"></param> - /// <param name="imageEncoder"></param> - /// <param name="libraryManager"></param> - /// <param name="mediaEncoder"></param> + /// <param name="logger">The logger.</param> + /// <param name="appPaths">The server application paths.</param> + /// <param name="fileSystem">The filesystem.</param> + /// <param name="imageEncoder">The image encoder.</param> + /// <param name="libraryManager">The library manager.</param> + /// <param name="mediaEncoder">The media encoder.</param> public ImageProcessor( ILogger<ImageProcessor> logger, IServerApplicationPaths appPaths, @@ -67,16 +61,10 @@ namespace Emby.Drawing _libraryManager = libraryManager; _mediaEncoder = mediaEncoder; _appPaths = appPaths; - - ImageEnhancers = Array.Empty<IImageEnhancer>(); - - ImageHelper.ImageProcessor = this; } private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); - private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images"); - /// <inheritdoc /> public IReadOnlyCollection<string> SupportedInputFormats => new HashSet<string>(StringComparer.OrdinalIgnoreCase) @@ -89,9 +77,7 @@ namespace Emby.Drawing "aiff", "cr2", "crw", - - // Remove until supported - //"nef", + "nef", "orf", "pef", "arw", @@ -111,19 +97,9 @@ namespace Emby.Drawing }; /// <inheritdoc /> - public IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; } - - /// <inheritdoc /> public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation; /// <inheritdoc /> - public IImageEncoder ImageEncoder - { - get => _imageEncoder; - set => _imageEncoder = value ?? throw new ArgumentNullException(nameof(value)); - } - - /// <inheritdoc /> public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) { var file = await ProcessImage(options).ConfigureAwait(false); @@ -150,6 +126,8 @@ namespace Emby.Drawing throw new ArgumentNullException(nameof(options)); } + var libraryManager = _libraryManager(); + ItemImageInfo originalImage = options.Image; BaseItem item = options.Item; @@ -157,9 +135,10 @@ namespace Emby.Drawing { if (item == null) { - item = _libraryManager().GetItemById(options.ItemId); + item = libraryManager.GetItemById(options.ItemId); } - originalImage = await _libraryManager().ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false); + + originalImage = await libraryManager.ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false); } string originalImagePath = originalImage.Path; @@ -186,27 +165,6 @@ namespace Emby.Drawing dateModified = supportedImageInfo.dateModified; bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); - if (options.Enhancers.Count > 0) - { - if (item == null) - { - item = _libraryManager().GetItemById(options.ItemId); - } - - var tuple = await GetEnhancedImage(new ItemImageInfo - { - DateModified = dateModified, - Type = originalImage.Type, - Path = originalImagePath - }, requiresTransparency, item, options.ImageIndex, options.Enhancers, CancellationToken.None).ConfigureAwait(false); - - originalImagePath = tuple.path; - dateModified = tuple.dateModified; - requiresTransparency = tuple.transparent; - // TODO: Get this info - originalImageSize = null; - } - bool autoOrient = false; ImageOrientation? orientation = null; if (item is Photo photo) @@ -239,12 +197,6 @@ namespace Emby.Drawing ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); string cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); - CheckDisposed(); - - LockInfo lockInfo = GetLock(cacheFilePath); - - await lockInfo.Lock.WaitAsync().ConfigureAwait(false); - try { if (!File.Exists(cacheFilePath)) @@ -270,10 +222,6 @@ namespace Emby.Drawing _logger.LogError(ex, "Error encoding image"); return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } - finally - { - ReleaseLock(cacheFilePath, lockInfo); - } } private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency) @@ -305,20 +253,18 @@ namespace Emby.Drawing } private string GetMimeType(ImageFormat format, string path) - { - switch(format) - { - case ImageFormat.Bmp: return MimeTypes.GetMimeType("i.bmp"); - case ImageFormat.Gif: return MimeTypes.GetMimeType("i.gif"); - case ImageFormat.Jpg: return MimeTypes.GetMimeType("i.jpg"); - case ImageFormat.Png: return MimeTypes.GetMimeType("i.png"); - case ImageFormat.Webp: return MimeTypes.GetMimeType("i.webp"); - default: return MimeTypes.GetMimeType(path); - } - } + => format switch + { + ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"), + ImageFormat.Gif => MimeTypes.GetMimeType("i.gif"), + ImageFormat.Jpg => MimeTypes.GetMimeType("i.jpg"), + ImageFormat.Png => MimeTypes.GetMimeType("i.png"), + ImageFormat.Webp => MimeTypes.GetMimeType("i.webp"), + _ => MimeTypes.GetMimeType(path) + }; /// <summary> - /// Gets the cache file path based on a set of parameters + /// Gets the cache file path based on a set of parameters. /// </summary> private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer) { @@ -400,11 +346,7 @@ namespace Emby.Drawing /// <inheritdoc /> public string GetImageCacheTag(BaseItem item, ItemImageInfo image) - { - var supportedEnhancers = GetSupportedEnhancers(item, image.Type).ToArray(); - - return GetImageCacheTag(item, image, supportedEnhancers); - } + => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); /// <inheritdoc /> public string GetImageCacheTag(BaseItem item, ChapterInfo chapter) @@ -424,26 +366,6 @@ namespace Emby.Drawing } } - /// <inheritdoc /> - public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers) - { - string originalImagePath = image.Path; - DateTime dateModified = image.DateModified; - ImageType imageType = image.Type; - - // Optimization - if (imageEnhancers.Count == 0) - { - return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); - } - - // Cache name is created with supported enhancers combined with the last config change so we pick up new config changes - var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList(); - cacheKeys.Add(originalImagePath + dateModified.Ticks); - - return string.Join("|", cacheKeys).GetMD5().ToString("N", CultureInfo.InvariantCulture); - } - private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) { var inputFormat = Path.GetExtension(originalImagePath) @@ -487,154 +409,6 @@ namespace Emby.Drawing return (originalImagePath, dateModified); } - /// <inheritdoc /> - public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex) - { - var enhancers = GetSupportedEnhancers(item, imageType).ToArray(); - - ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex); - - bool inputImageSupportsTransparency = SupportsTransparency(imageInfo.Path); - - var result = await GetEnhancedImage(imageInfo, inputImageSupportsTransparency, item, imageIndex, enhancers, CancellationToken.None); - - return result.path; - } - - private async Task<(string path, DateTime dateModified, bool transparent)> GetEnhancedImage( - ItemImageInfo image, - bool inputImageSupportsTransparency, - BaseItem item, - int imageIndex, - IReadOnlyCollection<IImageEnhancer> enhancers, - CancellationToken cancellationToken) - { - var originalImagePath = image.Path; - var dateModified = image.DateModified; - var imageType = image.Type; - - try - { - var cacheGuid = GetImageCacheTag(item, image, enhancers); - - // Enhance if we have enhancers - var enhancedImageInfo = await GetEnhancedImageInternal(originalImagePath, item, imageType, imageIndex, enhancers, cacheGuid, cancellationToken).ConfigureAwait(false); - - string enhancedImagePath = enhancedImageInfo.path; - - // If the path changed update dateModified - if (!string.Equals(enhancedImagePath, originalImagePath, StringComparison.OrdinalIgnoreCase)) - { - var treatmentRequiresTransparency = enhancedImageInfo.transparent; - - return (enhancedImagePath, _fileSystem.GetLastWriteTimeUtc(enhancedImagePath), treatmentRequiresTransparency); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error enhancing image"); - } - - return (originalImagePath, dateModified, inputImageSupportsTransparency); - } - - /// <summary> - /// Gets the enhanced image internal. - /// </summary> - /// <param name="originalImagePath">The original image path.</param> - /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <param name="supportedEnhancers">The supported enhancers.</param> - /// <param name="cacheGuid">The cache unique identifier.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task<System.String>.</returns> - /// <exception cref="ArgumentNullException"> - /// originalImagePath - /// or - /// item - /// </exception> - private async Task<(string path, bool transparent)> GetEnhancedImageInternal( - string originalImagePath, - BaseItem item, - ImageType imageType, - int imageIndex, - IReadOnlyCollection<IImageEnhancer> supportedEnhancers, - string cacheGuid, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(originalImagePath)) - { - throw new ArgumentNullException(nameof(originalImagePath)); - } - - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - var treatmentRequiresTransparency = false; - foreach (var enhancer in supportedEnhancers) - { - if (!treatmentRequiresTransparency) - { - treatmentRequiresTransparency = enhancer.GetEnhancedImageInfo(item, originalImagePath, imageType, imageIndex).RequiresTransparency; - } - } - - // All enhanced images are saved as png to allow transparency - string cacheExtension = _imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp) ? - ".webp" : - (treatmentRequiresTransparency ? ".png" : ".jpg"); - - string enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + cacheExtension); - - LockInfo lockInfo = GetLock(enhancedImagePath); - - await lockInfo.Lock.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - // Check again in case of contention - if (File.Exists(enhancedImagePath)) - { - return (enhancedImagePath, treatmentRequiresTransparency); - } - - Directory.CreateDirectory(Path.GetDirectoryName(enhancedImagePath)); - - await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false); - - return (enhancedImagePath, treatmentRequiresTransparency); - } - finally - { - ReleaseLock(enhancedImagePath, lockInfo); - } - } - - /// <summary> - /// Executes the image enhancers. - /// </summary> - /// <param name="imageEnhancers">The image enhancers.</param> - /// <param name="inputPath">The input path.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <returns>Task{EnhancedImage}.</returns> - private static async Task ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, string inputPath, string outputPath, BaseItem item, ImageType imageType, int imageIndex) - { - // Run the enhancers sequentially in order of priority - foreach (var enhancer in imageEnhancers) - { - await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false); - - // Feed the output into the next enhancer as input - inputPath = outputPath; - } - } - /// <summary> /// Gets the cache path. /// </summary> @@ -647,7 +421,7 @@ namespace Emby.Drawing /// or /// uniqueName /// or - /// fileExtension + /// fileExtension. /// </exception> public string GetCachePath(string path, string uniqueName, string fileExtension) { @@ -680,7 +454,7 @@ namespace Emby.Drawing /// <exception cref="ArgumentNullException"> /// path /// or - /// filename + /// filename. /// </exception> public string GetCachePath(string path, string filename) { @@ -688,6 +462,7 @@ namespace Emby.Drawing { throw new ArgumentNullException(nameof(path)); } + if (string.IsNullOrEmpty(filename)) { throw new ArgumentNullException(nameof(filename)); @@ -709,74 +484,19 @@ namespace Emby.Drawing } /// <inheritdoc /> - public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType) - { - foreach (var i in ImageEnhancers) - { - if (i.Supports(item, imageType)) - { - yield return i; - } - } - } - - - private class LockInfo - { - public SemaphoreSlim Lock = new SemaphoreSlim(1, 1); - public int Count = 1; - } - - private LockInfo GetLock(string key) - { - lock (_locks) - { - if (_locks.TryGetValue(key, out LockInfo info)) - { - info.Count++; - } - else - { - info = new LockInfo(); - _locks[key] = info; - } - return info; - } - } - - private void ReleaseLock(string key, LockInfo info) + public void Dispose() { - info.Lock.Release(); - - lock (_locks) + if (_disposed) { - info.Count--; - if (info.Count <= 0) - { - _locks.Remove(key); - info.Lock.Dispose(); - } + return; } - } - - /// <inheritdoc /> - public void Dispose() - { - _disposed = true; - var disposable = _imageEncoder as IDisposable; - if (disposable != null) + if (_imageEncoder is IDisposable disposable) { disposable.Dispose(); } - } - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } + _disposed = true; } } } diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs index 9f21e93dc..748622102 100644 --- a/Emby.Naming/Audio/AudioFileParser.cs +++ b/Emby.Naming/Audio/AudioFileParser.cs @@ -8,19 +8,12 @@ using Emby.Naming.Common; namespace Emby.Naming.Audio { - public class AudioFileParser + public static class AudioFileParser { - private readonly NamingOptions _options; - - public AudioFileParser(NamingOptions options) - { - _options = options; - } - - public bool IsAudioFile(string path) + public static bool IsAudioFile(string path, NamingOptions options) { var extension = Path.GetExtension(path) ?? string.Empty; - return _options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); } } } diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 1554e61d8..b4f22ed69 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -277,7 +277,7 @@ namespace Emby.Naming.Common // This isn't a Kodi naming rule, but the expression below causes false positives, // so we make sure this one gets tested first. // "Foo Bar 889" - new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$") + new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/x]*$") { IsNamed = true }, diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs index 7715a16a4..79fdae573 100644 --- a/Emby.Naming/TV/SeasonPathParser.cs +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; using System.IO; -using System.Linq; namespace Emby.Naming.TV { @@ -29,14 +28,14 @@ namespace Emby.Naming.TV { var result = new SeasonPathParserResult(); - var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders); + var (seasonNumber, isSeasonFolder) = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders); - result.SeasonNumber = seasonNumberInfo.seasonNumber; + result.SeasonNumber = seasonNumber; if (result.SeasonNumber.HasValue) { result.Success = true; - result.IsSeasonFolder = seasonNumberInfo.isSeasonFolder; + result.IsSeasonFolder = isSeasonFolder; } return result; @@ -90,12 +89,10 @@ namespace Emby.Naming.TV // Look for one of the season folder names foreach (var name in _seasonFolderNames) { - var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase); - - if (index != -1) + if (filename.Contains(name, StringComparison.OrdinalIgnoreCase)) { var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase)); - if (result.Item1.HasValue) + if (result.seasonNumber.HasValue) { return result; } @@ -105,25 +102,32 @@ namespace Emby.Naming.TV } var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries); - var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue); - return (resultNumber, true); + for (int i = 0; i < parts.Length; i++) + { + if (TryGetSeasonNumberFromPart(parts[i], out int seasonNumber)) + { + return (seasonNumber, true); + } + } + + return (null, true); } - private static int? GetSeasonNumberFromPart(string part) + private static bool TryGetSeasonNumberFromPart(ReadOnlySpan<char> part, out int seasonNumber) { + seasonNumber = 0; if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase)) { - return null; + return false; } - part = part.Substring(1); - - if (int.TryParse(part, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) + if (int.TryParse(part.Slice(1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { - return value; + seasonNumber = value; + return true; } - return null; + return false; } /// <summary> @@ -131,7 +135,7 @@ namespace Emby.Naming.TV /// </summary> /// <param name="path">The path.</param> /// <returns>System.Nullable{System.Int32}.</returns> - private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(string path) + private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path) { var numericStart = -1; var length = 0; @@ -142,7 +146,7 @@ namespace Emby.Naming.TV // Find out where the numbers start, and then keep going until they end for (var i = 0; i < path.Length; i++) { - if (char.IsNumber(path, i)) + if (char.IsNumber(path[i])) { if (!hasOpenParenth) { @@ -177,7 +181,7 @@ namespace Emby.Naming.TV return (null, isSeasonFolder); } - return (int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder); + return (int.Parse(path.Slice(numericStart, length), provider: CultureInfo.InvariantCulture), isSeasonFolder); } } } diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index ea9a6d6c2..3e5d473ec 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -32,7 +32,7 @@ namespace Emby.Naming.Video if (rule.MediaType == MediaType.Audio) { - if (!new AudioFileParser(_options).IsAudioFile(path)) + if (!AudioFileParser.IsAudioFile(path, _options)) { return result; } diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index 8f210fa45..b9afe998b 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -194,7 +194,7 @@ namespace Emby.Naming.Video } } - private string GetRegexInput(FileSystemMetadata file) + private static string GetRegexInput(FileSystemMetadata file) { // For directories, dummy up an extension otherwise the expressions will fail var input = !file.IsDirectory @@ -204,7 +204,7 @@ namespace Emby.Naming.Video return Path.GetFileName(input); } - private Match FindMatch(FileSystemMetadata input, Regex regex, int offset) + private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset) { var regexInput = GetRegexInput(input); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 77fe1a3b4..fd0112266 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -755,7 +755,18 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(UserManager); - LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager); + MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( + LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(), + ServerConfigurationManager, + FileSystemManager, + ProcessFactory, + LocalizationManager, + () => SubtitleEncoder, + _configuration, + StartupOptions.FFmpegPath); + serviceCollection.AddSingleton(MediaEncoder); + + LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager, MediaEncoder); serviceCollection.AddSingleton(LibraryManager); var musicManager = new MusicManager(LibraryManager); @@ -844,17 +855,6 @@ namespace Emby.Server.Implementations ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); - MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( - LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(), - ServerConfigurationManager, - FileSystemManager, - ProcessFactory, - LocalizationManager, - () => SubtitleEncoder, - _configuration, - StartupOptions.FFmpegPath); - serviceCollection.AddSingleton(MediaEncoder); - EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); serviceCollection.AddSingleton(EncodingManager); @@ -1085,8 +1085,6 @@ namespace Emby.Server.Implementations GetExports<IMetadataSaver>(), GetExports<IExternalId>()); - ImageProcessor.ImageEnhancers = GetExports<IImageEnhancer>(); - LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>()); SubtitleManager.AddParts(GetExports<ISubtitleProvider>()); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index c514846e5..44f38504a 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3521,20 +3521,6 @@ namespace Emby.Server.Implementations.Data } var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); - if (includeTypes.Length == 1) - { - whereClauses.Add("type=@type"); - if (statement != null) - { - statement.TryBind("@type", includeTypes[0]); - } - } - else if (includeTypes.Length > 1) - { - var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'")); - whereClauses.Add($"type in ({inClause})"); - } - // Only specify excluded types if no included types are specified if (includeTypes.Length == 0) { @@ -3553,6 +3539,19 @@ namespace Emby.Server.Implementations.Data whereClauses.Add($"type not in ({inClause})"); } } + else if (includeTypes.Length == 1) + { + whereClauses.Add("type=@type"); + if (statement != null) + { + statement.TryBind("@type", includeTypes[0]); + } + } + else if (includeTypes.Length > 1) + { + var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'")); + whereClauses.Add($"type in ({inClause})"); + } if (query.ChannelIds.Length == 1) { @@ -4927,7 +4926,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type // Not crazy about having this all the way down here, but at least it's in one place readonly Dictionary<string, string[]> _types = GetTypeMapDictionary(); - private IEnumerable<string> MapIncludeItemTypes(string value) + private string[] MapIncludeItemTypes(string value) { if (_types.TryGetValue(value, out string[] result)) { @@ -5611,32 +5610,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return counts; } - private List<Tuple<int, string>> GetItemValuesToSave(BaseItem item, List<string> inheritedTags) + private List<(int, string)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags) { - var list = new List<Tuple<int, string>>(); + var list = new List<(int, string)>(); if (item is IHasArtist hasArtist) { - list.AddRange(hasArtist.Artists.Select(i => new Tuple<int, string>(0, i))); + list.AddRange(hasArtist.Artists.Select(i => (0, i))); } if (item is IHasAlbumArtist hasAlbumArtist) { - list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => new Tuple<int, string>(1, i))); + list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (1, i))); } - list.AddRange(item.Genres.Select(i => new Tuple<int, string>(2, i))); - list.AddRange(item.Studios.Select(i => new Tuple<int, string>(3, i))); - list.AddRange(item.Tags.Select(i => new Tuple<int, string>(4, i))); + list.AddRange(item.Genres.Select(i => (2, i))); + list.AddRange(item.Studios.Select(i => (3, i))); + list.AddRange(item.Tags.Select(i => (4, i))); // keywords was 5 - list.AddRange(inheritedTags.Select(i => new Tuple<int, string>(6, i))); + list.AddRange(inheritedTags.Select(i => (6, i))); return list; } - private void UpdateItemValues(Guid itemId, List<Tuple<int, string>> values, IDatabaseConnection db) + private void UpdateItemValues(Guid itemId, List<(int, string)> values, IDatabaseConnection db) { if (itemId.Equals(Guid.Empty)) { @@ -5658,7 +5657,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type InsertItemValues(guidBlob, values, db); } - private void InsertItemValues(byte[] idBlob, List<Tuple<int, string>> values, IDatabaseConnection db) + private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db) { var startIndex = 0; var limit = 100; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 2bd0b840a..4f8f9f23b 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -142,11 +142,10 @@ namespace Emby.Server.Implementations.Devices public QueryResult<DeviceInfo> GetDevices(DeviceQuery query) { - var sessions = _authRepo.Get(new AuthenticationInfoQuery + IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery { //UserId = query.UserId HasUser = true - }).Items; // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger. @@ -154,23 +153,19 @@ namespace Emby.Server.Implementations.Devices { var val = query.SupportsSync.Value; - sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val).ToArray(); + sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val); } if (!query.UserId.Equals(Guid.Empty)) { var user = _userManager.GetUserById(query.UserId); - sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)).ToArray(); + sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); } var array = sessions.Select(ToDeviceInfo).ToArray(); - return new QueryResult<DeviceInfo> - { - Items = array, - TotalRecordCount = array.Length - }; + return new QueryResult<DeviceInfo>(array); } private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo) @@ -186,7 +181,7 @@ namespace Emby.Server.Implementations.Devices LastUserName = authInfo.UserName, Name = authInfo.DeviceName, DateLastActivity = authInfo.DateLastActivity, - IconUrl = caps == null ? null : caps.IconUrl + IconUrl = caps?.IconUrl }; } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index fcf0360c7..960f3f2d6 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1362,56 +1362,33 @@ namespace Emby.Server.Implementations.Dto return null; } - var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary).ToArray(); - ImageDimensions size; var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio(); if (defaultAspectRatio > 0) { - if (supportedEnhancers.Length == 0) - { - return defaultAspectRatio; - } + return defaultAspectRatio; + } - int dummyWidth = 200; - int dummyHeight = Convert.ToInt32(dummyWidth / defaultAspectRatio); - size = new ImageDimensions(dummyWidth, dummyHeight); + if (!imageInfo.IsLocalFile) + { + return null; } - else + + try { - if (!imageInfo.IsLocalFile) - { - return null; - } + size = _imageProcessor.GetImageDimensions(item, imageInfo); - try + if (size.Width <= 0 || size.Height <= 0) { - size = _imageProcessor.GetImageDimensions(item, imageInfo); - - if (size.Width <= 0 || size.Height <= 0) - { - return null; - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path); return null; } } - - foreach (var enhancer in supportedEnhancers) + catch (Exception ex) { - try - { - size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in image enhancer: {0}", enhancer.GetType().Name); - } + _logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path); + return null; } var width = size.Width; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index d983c1dc6..5d16a9050 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -29,11 +29,13 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -141,6 +143,7 @@ namespace Emby.Server.Implementations.Library public bool IsScanRunning { get; private set; } private IServerApplicationHost _appHost; + private readonly IMediaEncoder _mediaEncoder; /// <summary> /// The _library items cache @@ -174,7 +177,8 @@ namespace Emby.Server.Implementations.Library Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem, Func<IProviderManager> providerManagerFactory, - Func<IUserViewManager> userviewManager) + Func<IUserViewManager> userviewManager, + IMediaEncoder mediaEncoder) { _appHost = appHost; _logger = loggerFactory.CreateLogger(nameof(LibraryManager)); @@ -186,6 +190,7 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _providerManagerFactory = providerManagerFactory; _userviewManager = userviewManager; + _mediaEncoder = mediaEncoder; _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>(); @@ -1169,7 +1174,6 @@ namespace Emby.Server.Implementations.Library return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath) .Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue)) - .OrderBy(i => i.Name) .ToList(); } @@ -1401,25 +1405,32 @@ namespace Emby.Server.Implementations.Library private void SetTopParentOrAncestorIds(InternalItemsQuery query) { - if (query.AncestorIds.Length == 0) + var ancestorIds = query.AncestorIds; + int len = ancestorIds.Length; + if (len == 0) { return; } - var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList(); - - if (parents.All(i => i is ICollectionFolder || i is UserView)) + var parents = new BaseItem[len]; + for (int i = 0; i < len; i++) { - // Optimize by querying against top level views - query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); - query.AncestorIds = Array.Empty<Guid>(); - - // Prevent searching in all libraries due to empty filter - if (query.TopParentIds.Length == 0) + parents[i] = GetItemById(ancestorIds[i]); + if (!(parents[i] is ICollectionFolder || parents[i] is UserView)) { - query.TopParentIds = new[] { Guid.NewGuid() }; + return; } } + + // Optimize by querying against top level views + query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); + query.AncestorIds = Array.Empty<Guid>(); + + // Prevent searching in all libraries due to empty filter + if (query.TopParentIds.Length == 0) + { + query.TopParentIds = new[] { Guid.NewGuid() }; + } } public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query) @@ -1580,7 +1591,7 @@ namespace Emby.Server.Implementations.Library public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user) { var tasks = IntroProviders - .OrderBy(i => i.GetType().Name.IndexOf("Default", StringComparison.OrdinalIgnoreCase) == -1 ? 0 : 1) + .OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0) .Take(1) .Select(i => GetIntros(i, item, user)); @@ -2358,33 +2369,22 @@ namespace Emby.Server.Implementations.Library new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); } - public bool IsVideoFile(string path, LibraryOptions libraryOptions) + /// <inheritdoc /> + public bool IsVideoFile(string path) { var resolver = new VideoResolver(GetNamingOptions()); return resolver.IsVideoFile(path); } - public bool IsVideoFile(string path) - { - return IsVideoFile(path, new LibraryOptions()); - } - - public bool IsAudioFile(string path, LibraryOptions libraryOptions) - { - var parser = new AudioFileParser(GetNamingOptions()); - return parser.IsAudioFile(path); - } - + /// <inheritdoc /> public bool IsAudioFile(string path) - { - return IsAudioFile(path, new LibraryOptions()); - } + => AudioFileParser.IsAudioFile(path, GetNamingOptions()); + /// <inheritdoc /> public int? GetSeasonNumberFromPath(string path) - { - return SeasonPathParser.Parse(path, true, true).SeasonNumber; - } + => SeasonPathParser.Parse(path, true, true).SeasonNumber; + /// <inheritdoc /> public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) { var series = episode.Series; @@ -2408,6 +2408,38 @@ namespace Emby.Server.Implementations.Library episodeInfo = new Naming.TV.EpisodeInfo(); } + try + { + var libraryOptions = GetLibraryOptions(episode); + if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase)) + { + // Read from metadata + var mediaInfo = _mediaEncoder.GetMediaInfo(new MediaInfoRequest + { + MediaSource = episode.GetMediaSources(false)[0], + MediaType = DlnaProfileType.Video + }, CancellationToken.None).GetAwaiter().GetResult(); + if (mediaInfo.ParentIndexNumber > 0) + { + episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber; + } + + if (mediaInfo.IndexNumber > 0) + { + episodeInfo.EpisodeNumber = mediaInfo.IndexNumber; + } + + if (!string.IsNullOrEmpty(mediaInfo.ShowName)) + { + episodeInfo.SeriesName = mediaInfo.ShowName; + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading the episode informations with ffprobe. Episode: {EpisodeInfo}", episodeInfo.Path); + } + var changed = false; if (episodeInfo.IsByDate) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 7e3b27a12..524fb7c10 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -73,7 +73,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { // Return audio if the path is a file and has a matching extension - var libraryOptions = args.GetLibraryOptions(); var collectionType = args.GetCollectionType(); var isBooksCollectionType = string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase); @@ -92,7 +91,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); } - if (LibraryManager.IsAudioFile(args.Path, libraryOptions)) + if (LibraryManager.IsAudioFile(args.Path)) { var extension = Path.GetExtension(args.Path); @@ -105,7 +104,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var isMixedCollectionType = string.IsNullOrEmpty(collectionType); // For conflicting extensions, give priority to videos - if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path, libraryOptions)) + if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path)) { return null; } @@ -121,7 +120,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { item = new MediaBrowser.Controller.Entities.Audio.Audio(); } - else if (isBooksCollectionType) { item = new AudioBook(); diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 9f858f98d..85b1b6e32 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; @@ -78,9 +77,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio /// <summary> /// Determine if the supplied file data points to a music album. /// </summary> - public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions) + public bool IsMusicAlbum(string path, IDirectoryService directoryService) { - return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, libraryOptions, _libraryManager); + return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager); } /// <summary> @@ -94,7 +93,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio if (args.IsDirectory) { // if (args.Parent is MusicArtist) return true; //saves us from testing children twice - if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, args.GetLibraryOptions(), _libraryManager)) + if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager)) { return true; } @@ -112,7 +111,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem, - LibraryOptions libraryOptions, ILibraryManager libraryManager) { var discSubfolderCount = 0; @@ -132,7 +130,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio } var path = fileSystemInfo.FullName; - var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager); + var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); if (hasMusic) { @@ -153,7 +151,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { var fullName = fileSystemInfo.FullName; - if (libraryManager.IsAudioFile(fullName, libraryOptions)) + if (libraryManager.IsAudioFile(fullName)) { return true; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index ee7e84929..013fdbf13 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -80,14 +80,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio } // Avoid mis-identifying top folders - if (args.Parent.IsRoot) return null; + if (args.Parent.IsRoot) + { + return null; + } var directoryService = args.DirectoryService; var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager); // If we contain an album assume we are an artist folder - return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null; + return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null; } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 43302bb3f..848cdb7bd 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -80,6 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers }; break; } + if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService)) { videoInfo = parser.ResolveDirectory(args.Path); @@ -137,7 +138,7 @@ namespace Emby.Server.Implementations.Library.Resolvers return null; } - if (LibraryManager.IsVideoFile(args.Path, args.GetLibraryOptions()) || videoInfo.IsStub) + if (LibraryManager.IsVideoFile(args.Path) || videoInfo.IsStub) { var path = args.Path; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 08db168bc..cb67c8aa7 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -436,7 +436,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (result.Items.Count == 1) { var videoPath = result.Items[0].Path; - var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, libraryOptions, videoPath, i.Name)); + var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, videoPath, i.Name)); if (!hasPhotos) { @@ -446,8 +446,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return movie; } } - - if (result.Items.Count == 0 && multiDiscFolders.Count > 0) + else if (result.Items.Count == 0 && multiDiscFolders.Count > 0) { return GetMultiDiscMovie<T>(multiDiscFolders, directoryService); } @@ -519,14 +518,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } + int additionalPartsLen = folderPaths.Count - 1; + var additionalParts = new string[additionalPartsLen]; + folderPaths.CopyTo(1, additionalParts, 0, additionalPartsLen); + var returnVideo = new T { Path = folderPaths[0], - - AdditionalParts = folderPaths.Skip(1).ToArray(), - + AdditionalParts = additionalParts, VideoType = videoTypes[0], - Name = result[0].Name }; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 4536b0aaa..3ac837057 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -63,13 +63,12 @@ namespace Emby.Server.Implementations.Library.Resolvers { if (!file.IsDirectory && PhotoResolver.IsImageFile(file.FullName, _imageProcessor)) { - var libraryOptions = args.GetLibraryOptions(); var filename = file.Name; var ownedByMedia = false; foreach (var siblingFile in files) { - if (PhotoResolver.IsOwnedByMedia(_libraryManager, libraryOptions, siblingFile.FullName, filename)) + if (PhotoResolver.IsOwnedByMedia(_libraryManager, siblingFile.FullName, filename)) { ownedByMedia = true; break; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index e1eb23652..8ad546f8e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -8,7 +8,6 @@ using System.Linq; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; namespace Emby.Server.Implementations.Library.Resolvers @@ -57,11 +56,10 @@ namespace Emby.Server.Implementations.Library.Resolvers // Make sure the image doesn't belong to a video file var files = args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)); - var libraryOptions = args.GetLibraryOptions(); foreach (var file in files) { - if (IsOwnedByMedia(_libraryManager, libraryOptions, file.FullName, filename)) + if (IsOwnedByMedia(_libraryManager, file.FullName, filename)) { return null; } @@ -78,17 +76,17 @@ namespace Emby.Server.Implementations.Library.Resolvers return null; } - internal static bool IsOwnedByMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename) + internal static bool IsOwnedByMedia(ILibraryManager libraryManager, string file, string imageFilename) { - if (libraryManager.IsVideoFile(file, libraryOptions)) + if (libraryManager.IsVideoFile(file)) { - return IsOwnedByResolvedMedia(libraryManager, libraryOptions, file, imageFilename); + return IsOwnedByResolvedMedia(libraryManager, file, imageFilename); } return false; } - internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename) + internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, string file, string imageFilename) => imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase); internal static bool IsImageFile(string path, IImageProcessor imageProcessor) diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 4ee30b475..b547fc8c9 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return null; } - if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false)) + if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, false)) { return new Series { @@ -123,24 +123,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, - LibraryOptions libraryOptions, bool isTvContentType) { foreach (var child in fileSystemChildren) { - //if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) - //{ - // //logger.LogDebug("Igoring series file or folder marked hidden: {0}", child.FullName); - // continue; - //} - - // Can't enforce this because files saved by Bitcasa are always marked System - //if ((attributes & FileAttributes.System) == FileAttributes.System) - //{ - // logger.LogDebug("Igoring series subfolder marked system: {0}", child.FullName); - // continue; - //} - if (child.IsDirectory) { if (IsSeasonFolder(child.FullName, isTvContentType, libraryManager)) @@ -152,7 +138,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV else { string fullName = child.FullName; - if (libraryManager.IsVideoFile(fullName, libraryOptions)) + if (libraryManager.IsVideoFile(fullName)) { if (isTvContentType) { diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json index 0967ef424..1ac8d3ae4 100644 --- a/Emby.Server.Implementations/Localization/Core/es_DO.json +++ b/Emby.Server.Implementations/Localization/Core/es_DO.json @@ -1 +1,14 @@ -{} +{ + "Channels": "Canales", + "Books": "Libros", + "Albums": "Álbumes", + "Collections": "Colecciones", + "Artists": "Artistas", + "DeviceOnlineWithName": "{0} está conectado", + "DeviceOfflineWithName": "{0} ha desconectado", + "ChapterNameValue": "Capítulo {0}", + "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}", + "AuthenticationSucceededWithUserName": "{0} autenticado con éxito", + "Application": "Aplicación", + "AppDeviceValues": "App: {0}, Dispositivo: {1}" +} diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json index c3b5211b8..3490a7302 100644 --- a/Emby.Server.Implementations/Localization/Core/is.json +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -3,7 +3,7 @@ "ItemRemovedWithName": "{0} var fjarlægt úr safninu", "ItemAddedWithName": "{0} var bætt í safnið", "Inherit": "Erfa", - "HomeVideos": "Myndbönd að heiman", + "HomeVideos": "Heimamyndbönd", "HeaderRecordingGroups": "Upptökuhópar", "HeaderNextUp": "Næst á dagskrá", "HeaderLiveTV": "Sjónvarp í beinni útsendingu", @@ -36,10 +36,10 @@ "NotificationOptionVideoPlaybackStopped": "Myndbandafspilun stöðvuð", "NotificationOptionVideoPlayback": "Myndbandafspilun hafin", "NotificationOptionUserLockedOut": "Notandi læstur úti", - "NotificationOptionServerRestartRequired": "Endurræsing miðlara nauðsynileg", + "NotificationOptionServerRestartRequired": "Endurræsing þjóns er nauðsynileg", "NotificationOptionPluginUpdateInstalled": "Viðbótar uppfærsla uppsett", "NotificationOptionPluginUninstalled": "Viðbót fjarlægð", - "NotificationOptionPluginInstalled": "Viðbót settur upp", + "NotificationOptionPluginInstalled": "Viðbót sett upp", "NotificationOptionPluginError": "Bilun í viðbót", "NotificationOptionInstallationFailed": "Uppsetning tókst ekki", "NotificationOptionCameraImageUploaded": "Myndavélarmynd hlaðið upp", @@ -50,15 +50,15 @@ "NameSeasonUnknown": "Sería óþekkt", "NameSeasonNumber": "Sería {0}", "MixedContent": "Blandað efni", - "MessageServerConfigurationUpdated": "Stillingar miðlarans hefur verið uppfærð", - "MessageApplicationUpdatedTo": "Jellyfin Server hefur verið uppfærður í {0}", - "MessageApplicationUpdated": "Jellyfin Server hefur verið uppfærður", + "MessageServerConfigurationUpdated": "Stillingar þjóns hafa verið uppfærðar", + "MessageApplicationUpdatedTo": "Jellyfin þjónn hefur verið uppfærður í {0}", + "MessageApplicationUpdated": "Jellyfin þjónn hefur verið uppfærður", "Latest": "Nýjasta", - "LabelRunningTimeValue": "Keyrslutími kerfis: {0}", + "LabelRunningTimeValue": "spilunartími: {0}", "User": "Notandi", "System": "Kerfi", "NotificationOptionNewLibraryContent": "Nýju efni bætt við", - "NewVersionIsAvailable": "Ný útgáfa af Jellyfin Server er fáanleg til niðurhals.", + "NewVersionIsAvailable": "Ný útgáfa af Jellyfin þjón er fáanleg til niðurhals.", "NameInstallFailed": "{0} uppsetning mistókst", "MusicVideos": "Tónlistarmyndbönd", "Music": "Tónlist", @@ -74,5 +74,23 @@ "PluginUpdatedWithName": "{0} var uppfært", "PluginUninstalledWithName": "{0} var fjarlægt", "PluginInstalledWithName": "{0} var sett upp", - "NotificationOptionTaskFailed": "Tímasett verkefni mistókst" + "NotificationOptionTaskFailed": "Tímasett verkefni mistókst", + "StartupEmbyServerIsLoading": "Jellyfin netþjónnin er að hlaðast. Vinsamlega prufaðu aftur fljótlega.", + "VersionNumber": "Útgáfa {0}", + "ValueHasBeenAddedToLibrary": "{0} hefur verið bætt við í gagnasafnið þitt", + "UserStoppedPlayingItemWithValues": "{0} hefur lokið spilunar af {1} á {2}", + "UserStartedPlayingItemWithValues": "{0} er að spila {1} á {2}", + "UserPolicyUpdatedWithName": "Notandaregla hefur verið uppfærð fyrir notanda {0}", + "UserPasswordChangedWithName": "Lykilorði fyrir notandann {0} hefur verið breytt", + "UserOnlineFromDevice": "{0} hefur verið virkur síðan {1}", + "UserOfflineFromDevice": "{0} hefur aftengst frá {1}", + "UserLockedOutWithName": "Notanda {0} hefur verið hindraður aðgangur", + "UserDownloadingItemWithValues": "{0} Hleður niður {1}", + "SubtitlesDownloadedForItem": "Skjátextum halað niður fyrir {0}", + "SubtitleDownloadFailureFromForItem": "Tókst ekki að hala niður skjátextum frá {0} til {1}", + "ProviderValue": "Veitandi: {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Stilling {0} hefur verið uppfærð á netþjón", + "ValueSpecialEpisodeName": "Sérstakt - {0}", + "Shows": "Þættir", + "Playlists": "Spilunarlisti" } diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index e0daac8a5..dd6168614 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -89,8 +89,8 @@ "UserOnlineFromDevice": "{0} 在线,来自 {1}", "UserPasswordChangedWithName": "已为用户 {0} 更改密码", "UserPolicyUpdatedWithName": "用户协议已经被更新为 {0}", - "UserStartedPlayingItemWithValues": "{0} 已开始播放 {1}", - "UserStoppedPlayingItemWithValues": "{0} 已停止播放 {1}", + "UserStartedPlayingItemWithValues": "{0} 已在 {2} 上开始播放 {1}", + "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}", "ValueHasBeenAddedToLibrary": "{0} 已添加至您的媒体库中", "ValueSpecialEpisodeName": "特典 - {0}", "VersionNumber": "版本 {0}" diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 33fcb2d37..f3d9e5fce 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -1,97 +1,97 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", + "Albums": "專輯", + "AppDeviceValues": "軟體: {0}, 設備: {1}", + "Application": "應用程式", "Artists": "藝人", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", - "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continue Watching", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favorite Shows", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", + "AuthenticationSucceededWithUserName": "{0} 授權成功", + "Books": "圖書", + "CameraImageUploadedFrom": "{0} 成功上傳一張新相片", + "Channels": "頻道", + "ChapterNameValue": "章節 {0}", + "Collections": "合輯", + "DeviceOfflineWithName": "{0} 已經斷開連結", + "DeviceOnlineWithName": "{0} 已經連接", + "FailedLoginAttemptWithUserName": "來自 {0} 的失敗登入嘗試", + "Favorites": "我的最愛", + "Folders": "檔案夾", + "Genres": "風格", + "HeaderAlbumArtists": "專輯藝術家", + "HeaderCameraUploads": "相機上載", + "HeaderContinueWatching": "繼續觀看", + "HeaderFavoriteAlbums": "最愛專輯", + "HeaderFavoriteArtists": "最愛藝術家", + "HeaderFavoriteEpisodes": "最愛的劇集", + "HeaderFavoriteShows": "最愛的節目", + "HeaderFavoriteSongs": "最愛的歌曲", + "HeaderLiveTV": "電視直播", + "HeaderNextUp": "接下來", + "HeaderRecordingGroups": "錄製組", + "HomeVideos": "家庭影片", + "Inherit": "繼承", + "ItemAddedWithName": "{0} 已添加至媒體庫", + "ItemRemovedWithName": "{0} 已從媒體庫移除", + "LabelIpAddressValue": "IP 地址: {0}", + "LabelRunningTimeValue": "運行時間: {0}", + "Latest": "最新", + "MessageApplicationUpdated": "Jellyfin Server 已更新", + "MessageApplicationUpdatedTo": "Jellyfin 伺服器已更新至 {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已更新", + "MessageServerConfigurationUpdated": "伺服器設定已經更新", "MixedContent": "Mixed content", - "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", - "Playlists": "Playlists", + "Movies": "電影", + "Music": "音樂", + "MusicVideos": "音樂MV", + "NameInstallFailed": "{0} 安裝失敗", + "NameSeasonNumber": "第 {0} 季", + "NameSeasonUnknown": "未知季數", + "NewVersionIsAvailable": "新版本的 Jellyfin 伺服器可供下載。", + "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新", + "NotificationOptionApplicationUpdateInstalled": "應用程式已更新", + "NotificationOptionAudioPlayback": "開始播放音頻", + "NotificationOptionAudioPlaybackStopped": "已停止播放音頻", + "NotificationOptionCameraImageUploaded": "相機相片已上傳", + "NotificationOptionInstallationFailed": "安裝失敗", + "NotificationOptionNewLibraryContent": "已添加新内容", + "NotificationOptionPluginError": "擴充元件錯誤", + "NotificationOptionPluginInstalled": "擴充元件已安裝", + "NotificationOptionPluginUninstalled": "擴充元件已移除", + "NotificationOptionPluginUpdateInstalled": "擴充元件更新已安裝", + "NotificationOptionServerRestartRequired": "伺服器需要重啓", + "NotificationOptionTaskFailed": "計劃任務失敗", + "NotificationOptionUserLockedOut": "用家已鎖定", + "NotificationOptionVideoPlayback": "開始播放視頻", + "NotificationOptionVideoPlaybackStopped": "已停止播放視頻", + "Photos": "相片", + "Playlists": "播放清單", "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", + "PluginInstalledWithName": "已安裝 {0}", + "PluginUninstalledWithName": "已移除 {0}", + "PluginUpdatedWithName": "已更新 {0}", "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", - "Shows": "Shows", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "ScheduledTaskFailedWithName": "{0} 任務失敗", + "ScheduledTaskStartedWithName": "{0} 任務開始", + "ServerNameNeedsToBeRestarted": "{0} 需要重啓", + "Shows": "節目", + "Songs": "歌曲", + "StartupEmbyServerIsLoading": "Jellyfin 伺服器載入中,請稍後再試。", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", + "SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕", + "SubtitlesDownloadedForItem": "已為 {0} 下載了字幕", + "Sync": "同步", "System": "System", - "TvShows": "TV Shows", + "TvShows": "電視節目", "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", - "ValueSpecialEpisodeName": "Special - {0}", + "UserCreatedWithName": "用家 {0} 已創建", + "UserDeletedWithName": "用家 {0} 已移除", + "UserDownloadingItemWithValues": "{0} 正在下載 {1}", + "UserLockedOutWithName": "用家 {0} 已被鎖定", + "UserOfflineFromDevice": "{0} 已從 {1} 斷開", + "UserOnlineFromDevice": "{0} 已連綫,來自 {1}", + "UserPasswordChangedWithName": "用家 {0} 的密碼已變更", + "UserPolicyUpdatedWithName": "用戶協議已被更新為 {0}", + "UserStartedPlayingItemWithValues": "{0} 正在 {2} 上播放 {1}", + "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}", + "ValueHasBeenAddedToLibrary": "{0} 已添加到你的媒體庫", + "ValueSpecialEpisodeName": "特典 - {0}", "VersionNumber": "版本{0}" } diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index 3b0dc4ab2..acd211f22 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -50,7 +50,7 @@ "NotificationOptionCameraImageUploaded": "相機相片已上傳", "NotificationOptionInstallationFailed": "安裝失敗", "NotificationOptionNewLibraryContent": "已新增新內容", - "NotificationOptionPluginError": "擴充元件安裝失敗", + "NotificationOptionPluginError": "擴充元件錯誤", "NotificationOptionPluginInstalled": "擴充元件已安裝", "NotificationOptionPluginUninstalled": "擴充元件已移除", "NotificationOptionPluginUpdateInstalled": "已更新擴充元件", diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index b080b3e6a..2ea690650 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -6,7 +6,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Globalization; using Microsoft.Extensions.Logging; using SkiaSharp; using static Jellyfin.Drawing.Skia.SkiaHelper; @@ -18,27 +17,23 @@ namespace Jellyfin.Drawing.Skia /// </summary> public class SkiaEncoder : IImageEncoder { - private readonly ILogger _logger; - private readonly IApplicationPaths _appPaths; - private readonly ILocalizationManager _localizationManager; - private static readonly HashSet<string> _transparentImageTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; + private readonly ILogger _logger; + private readonly IApplicationPaths _appPaths; + /// <summary> /// Initializes a new instance of the <see cref="SkiaEncoder"/> class. /// </summary> /// <param name="logger">The application logger.</param> /// <param name="appPaths">The application paths.</param> - /// <param name="localizationManager">The application localization manager.</param> public SkiaEncoder( ILogger<SkiaEncoder> logger, - IApplicationPaths appPaths, - ILocalizationManager localizationManager) + IApplicationPaths appPaths) { _logger = logger; _appPaths = appPaths; - _localizationManager = localizationManager; } /// <inheritdoc/> @@ -235,9 +230,12 @@ namespace Jellyfin.Drawing.Skia private bool RequiresSpecialCharacterHack(string path) { - if (_localizationManager.HasUnicodeCategory(path, UnicodeCategory.OtherLetter)) + for (int i = 0; i < path.Length; i++) { - return true; + if (char.GetUnicodeCategory(path[i]) == UnicodeCategory.OtherLetter) + { + return true; + } } if (HasDiacritics(path)) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 1b4280d82..1dd598236 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -168,7 +168,7 @@ namespace Jellyfin.Server _loggerFactory, options, new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths), - new NullImageEncoder(), + GetImageEncoder(appPaths), new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()), appConfig); try @@ -192,8 +192,6 @@ namespace Jellyfin.Server throw; } - appHost.ImageProcessor.ImageEncoder = GetImageEncoder(appPaths, appHost.LocalizationManager); - await appHost.RunStartupTasksAsync().ConfigureAwait(false); stopWatch.Stop(); @@ -491,9 +489,7 @@ namespace Jellyfin.Server } } - private static IImageEncoder GetImageEncoder( - IApplicationPaths appPaths, - ILocalizationManager localizationManager) + private static IImageEncoder GetImageEncoder(IApplicationPaths appPaths) { try { @@ -502,8 +498,7 @@ namespace Jellyfin.Server return new SkiaEncoder( _loggerFactory.CreateLogger<SkiaEncoder>(), - appPaths, - localizationManager); + appPaths); } catch (Exception ex) { diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index c55618aa1..af455987b 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -24,7 +24,7 @@ using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Images { /// <summary> - /// Class GetItemImage + /// Class GetItemImage. /// </summary> [Route("/Items/{Id}/Images", "GET", Summary = "Gets information about an item's images")] [Authenticated] @@ -558,21 +558,6 @@ namespace MediaBrowser.Api.Images throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", displayText, request.Type)); } - IImageEnhancer[] supportedImageEnhancers; - if (_imageProcessor.ImageEnhancers.Count > 0) - { - if (item == null) - { - item = _libraryManager.GetItemById(itemId); - } - - supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type).ToArray() : Array.Empty<IImageEnhancer>(); - } - else - { - supportedImageEnhancers = Array.Empty<IImageEnhancer>(); - } - bool cropwhitespace; if (request.CropWhitespace.HasValue) { @@ -598,25 +583,25 @@ namespace MediaBrowser.Api.Images {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"} }; - return GetImageResult(item, + return GetImageResult( + item, itemId, request, imageInfo, cropwhitespace, outputFormats, - supportedImageEnhancers, cacheDuration, responseHeaders, isHeadRequest); } - private async Task<object> GetImageResult(BaseItem item, + private async Task<object> GetImageResult( + BaseItem item, Guid itemId, ImageRequest request, ItemImageInfo image, bool cropwhitespace, IReadOnlyCollection<ImageFormat> supportedFormats, - IReadOnlyCollection<IImageEnhancer> enhancers, TimeSpan? cacheDuration, IDictionary<string, string> headers, bool isHeadRequest) @@ -624,7 +609,6 @@ namespace MediaBrowser.Api.Images var options = new ImageProcessingOptions { CropWhiteSpace = cropwhitespace, - Enhancers = enhancers, Height = request.Height, ImageIndex = request.Index ?? 0, Image = image, diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index a58a11bd1..79399807f 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; @@ -21,19 +20,11 @@ namespace MediaBrowser.Controller.Drawing IReadOnlyCollection<string> SupportedInputFormats { get; } /// <summary> - /// Gets the image enhancers. - /// </summary> - /// <value>The image enhancers.</value> - IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; } - - /// <summary> /// Gets a value indicating whether [supports image collage creation]. /// </summary> /// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value> bool SupportsImageCollageCreation { get; } - IImageEncoder ImageEncoder { get; set; } - /// <summary> /// Gets the dimensions of the image. /// </summary> @@ -59,14 +50,6 @@ namespace MediaBrowser.Controller.Drawing ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem); /// <summary> - /// Gets the supported enhancers. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> - /// <returns>IEnumerable{IImageEnhancer}.</returns> - IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType); - - /// <summary> /// Gets the image cache tag. /// </summary> /// <param name="item">The item.</param> @@ -76,15 +59,6 @@ namespace MediaBrowser.Controller.Drawing string GetImageCacheTag(BaseItem item, ChapterInfo info); /// <summary> - /// Gets the image cache tag. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="image">The image.</param> - /// <param name="imageEnhancers">The image enhancers.</param> - /// <returns>Guid.</returns> - string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers); - - /// <summary> /// Processes the image. /// </summary> /// <param name="options">The options.</param> @@ -100,15 +74,6 @@ namespace MediaBrowser.Controller.Drawing Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options); /// <summary> - /// Gets the enhanced image. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <returns>Task{System.String}.</returns> - Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex); - - /// <summary> /// Gets the supported image output formats. /// </summary> /// <returns><see cref="IReadOnlyCollection{ImageOutput}" />.</returns> diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index 432cf8042..d5a5f547e 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -19,8 +19,6 @@ namespace MediaBrowser.Controller.Drawing return GetSizeEstimate(options); } - public static IImageProcessor ImageProcessor { get; set; } - private static ImageDimensions GetSizeEstimate(ImageProcessingOptions options) { if (options.Width.HasValue && options.Height.HasValue) diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 29addf6e6..870e0278e 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; namespace MediaBrowser.Controller.Drawing @@ -34,8 +33,6 @@ namespace MediaBrowser.Controller.Drawing public int Quality { get; set; } - public IReadOnlyCollection<IImageEnhancer> Enhancers { get; set; } - public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; } public bool AddPlayedIndicator { get; set; } diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index 86d62add9..5ebc9f16a 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -41,10 +41,10 @@ namespace MediaBrowser.Controller.Entities public override double GetDefaultPrimaryImageAspectRatio() { // REVIEW: @bond - if (Width.HasValue && Height.HasValue) + if (Width != 0 && Height != 0) { - double width = Width.Value; - double height = Height.Value; + double width = Width; + double height = Height; if (Orientation.HasValue) { @@ -67,8 +67,6 @@ namespace MediaBrowser.Controller.Entities return base.GetDefaultPrimaryImageAspectRatio(); } - public new int? Width { get; set; } - public new int? Height { get; set; } public string CameraMake { get; set; } public string CameraModel { get; set; } public string Software { get; set; } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 511356aa4..2e1c97f67 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -157,7 +157,8 @@ namespace MediaBrowser.Controller.Library /// <param name="introProviders">The intro providers.</param> /// <param name="itemComparers">The item comparers.</param> /// <param name="postscanTasks">The postscan tasks.</param> - void AddParts(IEnumerable<IResolverIgnoreRule> rules, + void AddParts( + IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, @@ -349,9 +350,6 @@ namespace MediaBrowser.Controller.Library /// <returns><c>true</c> if [is audio file] [the specified path]; otherwise, <c>false</c>.</returns> bool IsAudioFile(string path); - bool IsAudioFile(string path, LibraryOptions libraryOptions); - bool IsVideoFile(string path, LibraryOptions libraryOptions); - /// <summary> /// Gets the season number from path. /// </summary> diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 35f188bb7..38ef33caf 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -316,11 +316,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue) { - var size = new ImageDimensions - { - Width = VideoStream.Width.Value, - Height = VideoStream.Height.Value - }; + var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value); var newSize = DrawingUtils.Resize(size, BaseRequest.Width ?? 0, @@ -346,11 +342,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue) { - var size = new ImageDimensions - { - Width = VideoStream.Width.Value, - Height = VideoStream.Height.Value - }; + var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value); var newSize = DrawingUtils.Resize(size, BaseRequest.Width ?? 0, diff --git a/MediaBrowser.Controller/Providers/IImageEnhancer.cs b/MediaBrowser.Controller/Providers/IImageEnhancer.cs deleted file mode 100644 index c27c00ca2..000000000 --- a/MediaBrowser.Controller/Providers/IImageEnhancer.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Controller.Providers -{ - public interface IImageEnhancer - { - /// <summary> - /// Return true only if the given image for the given item will be enhanced by this enhancer. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> - /// <returns><c>true</c> if this enhancer will enhance the supplied image for the supplied item, <c>false</c> otherwise</returns> - bool Supports(BaseItem item, ImageType imageType); - - /// <summary> - /// Gets the priority or order in which this enhancer should be run. - /// </summary> - /// <value>The priority.</value> - MetadataProviderPriority Priority { get; } - - /// <summary> - /// Return a key incorporating all configuration information related to this item - /// </summary> - /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> - /// <returns>Cache key relating to the current state of this item and configuration</returns> - string GetConfigurationCacheKey(BaseItem item, ImageType imageType); - - /// <summary> - /// Gets the size of the enhanced image. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <param name="originalImageSize">Size of the original image.</param> - /// <returns>ImageSize.</returns> - ImageDimensions GetEnhancedImageSize(BaseItem item, ImageType imageType, int imageIndex, ImageDimensions originalImageSize); - - EnhancedImageInfo GetEnhancedImageInfo(BaseItem item, string inputFile, ImageType imageType, int imageIndex); - - /// <summary> - /// Enhances the image async. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="inputFile">The input file.</param> - /// <param name="outputFile">The output file.</param> - /// <param name="imageType">Type of the image.</param> - /// <param name="imageIndex">Index of the image.</param> - /// <returns>Task{Image}.</returns> - /// <exception cref="System.ArgumentNullException"></exception> - Task EnhanceImageAsync(BaseItem item, string inputFile, string outputFile, ImageType imageType, int imageIndex); - } - - public class EnhancedImageInfo - { - public bool RequiresTransparency { get; set; } - } -} diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index bd89c6cae..f8047af42 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -112,6 +112,9 @@ namespace MediaBrowser.MediaEncoding.Probing info.Name = title; } + info.IndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_sort"); + info.ParentIndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "season_number"); + info.ShowName = FFProbeHelpers.GetDictionaryValue(tags, "show_name"); info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date"); // Several different forms of retaildate diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 3c99f9bb5..9d5d2b869 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -21,6 +21,7 @@ namespace MediaBrowser.Model.Configuration public bool ImportMissingEpisodes { get; set; } public bool EnableAutomaticSeriesGrouping { get; set; } public bool EnableEmbeddedTitles { get; set; } + public bool EnableEmbeddedEpisodeInfos { get; set; } public int AutomaticRefreshIntervalDays { get; set; } diff --git a/MediaBrowser.Model/Drawing/ImageDimensions.cs b/MediaBrowser.Model/Drawing/ImageDimensions.cs index b546dbb25..160be11f0 100644 --- a/MediaBrowser.Model/Drawing/ImageDimensions.cs +++ b/MediaBrowser.Model/Drawing/ImageDimensions.cs @@ -1,39 +1,46 @@ #pragma warning disable CS1591 #pragma warning disable SA1600 +using System.Globalization; + namespace MediaBrowser.Model.Drawing { /// <summary> /// Struct ImageDimensions. /// </summary> - public struct ImageDimensions + public readonly struct ImageDimensions { + public ImageDimensions(int width, int height) + { + Width = width; + Height = height; + } + /// <summary> - /// Gets or sets the height. + /// Gets the height. /// </summary> /// <value>The height.</value> - public int Height { get; set; } + public int Height { get; } /// <summary> - /// Gets or sets the width. + /// Gets the width. /// </summary> /// <value>The width.</value> - public int Width { get; set; } + public int Width { get; } public bool Equals(ImageDimensions size) { return Width.Equals(size.Width) && Height.Equals(size.Height); } + /// <inheritdoc /> public override string ToString() { - return string.Format("{0}-{1}", Width, Height); - } - - public ImageDimensions(int width, int height) - { - Width = width; - Height = height; + return string.Format( + CultureInfo.InvariantCulture, + "{0}-{1}", + Width, + Height); } } } diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs index 6ad766d39..237a2b36c 100644 --- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs +++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs @@ -36,6 +36,7 @@ namespace MediaBrowser.Model.MediaInfo /// <value>The studios.</value> public string[] Studios { get; set; } public string[] Genres { get; set; } + public string ShowName { get; set; } public int? IndexNumber { get; set; } public int? ParentIndexNumber { get; set; } public int? ProductionYear { get; set; } diff --git a/MediaBrowser.Model/Querying/QueryResult.cs b/MediaBrowser.Model/Querying/QueryResult.cs index ad89ae38d..5d4d6226b 100644 --- a/MediaBrowser.Model/Querying/QueryResult.cs +++ b/MediaBrowser.Model/Querying/QueryResult.cs @@ -30,5 +30,11 @@ namespace MediaBrowser.Model.Querying { Items = Array.Empty<T>(); } + + public QueryResult(IReadOnlyList<T> items) + { + Items = items; + TotalRecordCount = items.Count; + } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs index 93c59c9ca..5017fce4d 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs @@ -6,6 +6,21 @@ namespace Jellyfin.Naming.Tests.TV { public class EpisodeNumberTests { + private readonly NamingOptions _namingOptions = new NamingOptions(); + + [Theory] + [InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", 3)] + [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 22)] + [InlineData("Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", 1)] + [InlineData("After Life 1x06 Episodio 6 [WEB-DL NF 1080p h264 Dual DD 5.1 Sub].mkv", 6)] + public void GetEpisodeNumberFromFileTest(string path, int? expected) + { + var result = new EpisodePathParser(_namingOptions) + .Parse(path, false); + + Assert.Equal(expected, result.EpisodeNumber); + } + [Fact] public void TestEpisodeNumber1() { @@ -382,9 +397,7 @@ namespace Jellyfin.Naming.Tests.TV private int? GetEpisodeNumberFromFile(string path) { - var options = new NamingOptions(); - - var result = new EpisodePathParser(options) + var result = new EpisodePathParser(_namingOptions) .Parse(path, false); return result.EpisodeNumber; diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs index ba3c5ecac..1df28c974 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs @@ -6,11 +6,21 @@ namespace Jellyfin.Naming.Tests.TV { public class SeasonNumberTests { - private int? GetSeasonNumberFromEpisodeFile(string path) + private readonly NamingOptions _namingOptions = new NamingOptions(); + + [Theory] + [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 25)] + public void GetSeasonNumberFromEpisodeFileTest(string path, int? expected) { - var options = new NamingOptions(); + var result = new EpisodeResolver(_namingOptions) + .Resolve(path, false); - var result = new EpisodeResolver(options) + Assert.Equal(expected, result.SeasonNumber); + } + + private int? GetSeasonNumberFromEpisodeFile(string path) + { + var result = new EpisodeResolver(_namingOptions) .Resolve(path, false); return result.SeasonNumber; |
