From c52a2f2f7b130d73a96cdac00f1e63531a04139b Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Tue, 12 Jan 2021 21:35:06 +0100 Subject: [PATCH 001/167] Move studios image providers to plugin --- .../MediaBrowser.Providers.csproj | 2 + .../Configuration/PluginConfiguration.cs | 24 ++++++++ .../StudioImages/Configuration/config.html | 58 +++++++++++++++++++ .../Plugins/StudioImages/Plugin.cs | 43 ++++++++++++++ .../StudioImages}/StudiosImageProvider.cs | 11 ++-- 5 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs rename MediaBrowser.Providers/{Studios => Plugins/StudioImages}/StudiosImageProvider.cs (93%) diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 071a149db9..97d3a46692 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -51,5 +51,7 @@ + + diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs new file mode 100644 index 0000000000..fad989ab43 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs @@ -0,0 +1,24 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Providers.Plugins.StudioImages +{ + public class PluginConfiguration : BasePluginConfiguration + { + private string _repository = Plugin.DefaultServer; + + public string RepositoryUrl + { + get + { + return _repository; + } + + set + { + _repository = value.TrimEnd('/'); + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html new file mode 100644 index 0000000000..5ede1a2bbf --- /dev/null +++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html @@ -0,0 +1,58 @@ + + + + AudioDB + + +
+
+
+
+
+ +
This can be any Jellyfin-compatible artwork repository.
+
+
+
+ +
+
+
+
+ +
+ + diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs new file mode 100644 index 0000000000..69a0569e5c --- /dev/null +++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs @@ -0,0 +1,43 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.StudioImages +{ + public class Plugin : BasePlugin, IHasWebPages + { + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + public static Plugin Instance { get; private set; } + + public override Guid Id => new Guid("872a7849-1171-458d-a6fb-3de3d442ad30"); + + public override string Name => "Studio Images"; + + public override string Description => "Get artwork for studios from any Jellyfin-compatible repository."; + + // TODO change this for a Jellyfin-hosted repository. + public const string DefaultServer = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname"; + + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.StudioImages.xml"; + + public IEnumerable GetPages() + { + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html" + }; + } + } +} diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs similarity index 93% rename from MediaBrowser.Providers/Studios/StudiosImageProvider.cs rename to MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs index 90e13f12f8..1be3fbd049 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs @@ -15,6 +15,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.StudioImages; namespace MediaBrowser.Providers.Studios { @@ -23,15 +24,17 @@ namespace MediaBrowser.Providers.Studios private readonly IServerConfigurationManager _config; private readonly IHttpClientFactory _httpClientFactory; private readonly IFileSystem _fileSystem; + private readonly String repositoryUrl; public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem) { _config = config; _httpClientFactory = httpClientFactory; _fileSystem = fileSystem; + repositoryUrl = Plugin.Instance.Configuration.RepositoryUrl; } - public string Name => "Emby Designs"; + public string Name => "Artwork Repository"; public int Order => 0; @@ -104,19 +107,19 @@ namespace MediaBrowser.Providers.Studios private string GetUrl(string image, string filename) { - return string.Format(CultureInfo.InvariantCulture, "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studios/{0}/{1}.jpg", image, filename); + return string.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}.jpg", repositoryUrl, image, filename); } private Task EnsureThumbsList(string file, CancellationToken cancellationToken) { - const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studiothumbs.txt"; + string url = string.Format(CultureInfo.InvariantCulture, "{0}/studiothumbs.txt", repositoryUrl); return EnsureList(url, file, _fileSystem, cancellationToken); } private Task EnsurePosterList(string file, CancellationToken cancellationToken) { - const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studioposters.txt"; + string url = string.Format(CultureInfo.InvariantCulture, "{0}/studioposters.txt", repositoryUrl); return EnsureList(url, file, _fileSystem, cancellationToken); } From 80877aa945c5af9ffd6ca811d981ba417942ed9f Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 17 Apr 2021 09:27:58 +0100 Subject: [PATCH 002/167] Cleaned up "value assigned is not used in any execution path" --- Emby.Dlna/Service/BaseControlHandler.cs | 2 +- .../HttpServer/WebSocketConnection.cs | 4 ++-- .../LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 2 +- .../QuickConnect/QuickConnectManager.cs | 3 +-- Jellyfin.Api/Controllers/LibraryController.cs | 2 +- Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +- MediaBrowser.Controller/LiveTv/LiveTvChannel.cs | 4 +--- MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs | 8 ++++---- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 8 ++++---- MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs | 6 +++--- 10 files changed, 19 insertions(+), 22 deletions(-) diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index fda8346f9e..e4b9877e12 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -47,7 +47,7 @@ namespace Emby.Dlna.Service private async Task ProcessControlRequestInternalAsync(ControlRequest request) { - ControlRequestInfo requestInfo = null; + ControlRequestInfo requestInfo; using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8)) { diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 06acb56061..c661e6757f 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Buffers; @@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.HttpServer } WebSocketMessage? stub; - long bytesConsumed = 0; + long bytesConsumed; try { stub = DeserializeWebSocketMessage(buffer, out bytesConsumed); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index b16ccc561a..61262eb14e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -87,7 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host); var remoteAddress = IPAddress.Parse(uri.Host); - IPAddress localAddress = null; + IPAddress localAddress; using (var tcpClient = new TcpClient()) { try diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 7bed06de36..0e5f65c724 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -258,7 +258,6 @@ namespace Emby.Server.Implementations.QuickConnect } // Expire stale connection requests - var code = string.Empty; var values = _currentRequests.Values.ToList(); for (int i = 0; i < values.Count; i++) @@ -266,7 +265,7 @@ namespace Emby.Server.Implementations.QuickConnect var added = values[i].DateAdded ?? DateTime.UnixEpoch; if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll) { - code = values[i].Code; + var code = values[i].Code; _logger.LogDebug("Removing expired request {code}", code); if (!_currentRequests.TryRemove(code, out _)) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 1d4bbe61e8..83a4b7525a 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -732,7 +732,7 @@ namespace Jellyfin.Api.Controllers else { // For non series and movie types these columns are typically null - isSeries = null; + // isSeries = null; isMovie = null; includeItemTypes.Add(item.GetType().Name); } diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index e95410d024..445bad69d2 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -376,7 +376,7 @@ namespace Jellyfin.Api.Controllers } else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)) { - var outputFmp4HeaderArg = string.Empty; + string outputFmp4HeaderArg; var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); if (isWindows) { diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index ec933caf34..76dfcd28c1 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -61,9 +61,7 @@ namespace MediaBrowser.Controller.LiveTv { if (!string.IsNullOrEmpty(Number)) { - double number = 0; - - if (double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + if (double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out double number)) { return string.Format(CultureInfo.InvariantCulture, "{0:00000.0}", number) + "-" + (Name ?? string.Empty); } diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 7ad8c24e89..2e3ea91bfd 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -203,7 +203,7 @@ namespace MediaBrowser.LocalMetadata.Images added = AddImage(files, images, "logo", imagePrefix, isInMixedFolder, ImageType.Logo); if (!added) { - added = AddImage(files, images, "clearlogo", imagePrefix, isInMixedFolder, ImageType.Logo); + AddImage(files, images, "clearlogo", imagePrefix, isInMixedFolder, ImageType.Logo); } } @@ -220,7 +220,7 @@ namespace MediaBrowser.LocalMetadata.Images if (!added) { - added = AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc); + AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc); } } else if (item is Video || item is BoxSet) @@ -234,7 +234,7 @@ namespace MediaBrowser.LocalMetadata.Images if (!added) { - added = AddImage(files, images, "discart", imagePrefix, isInMixedFolder, ImageType.Disc); + AddImage(files, images, "discart", imagePrefix, isInMixedFolder, ImageType.Disc); } } @@ -250,7 +250,7 @@ namespace MediaBrowser.LocalMetadata.Images added = AddImage(files, images, "landscape", imagePrefix, isInMixedFolder, ImageType.Thumb); if (!added) { - added = AddImage(files, images, "thumb", imagePrefix, isInMixedFolder, ImageType.Thumb); + AddImage(files, images, "thumb", imagePrefix, isInMixedFolder, ImageType.Thumb); } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 74849a5221..d048d44b69 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -111,10 +111,10 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (streamFileNames == null) - { - streamFileNames = Array.Empty(); - } + // if (streamFileNames == null) + // { + // streamFileNames = Array.Empty(); + // } mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 9804ec3bb4..ab950040a7 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -75,21 +75,21 @@ namespace MediaBrowser.Providers.MediaInfo string[] subtitleDownloadLanguages; bool skipIfEmbeddedSubtitlesPresent; bool skipIfAudioTrackMatches; - bool requirePerfectMatch; + // bool requirePerfectMatch; if (libraryOptions.SubtitleDownloadLanguages == null) { subtitleDownloadLanguages = options.DownloadLanguages; skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; - requirePerfectMatch = options.RequirePerfectMatch; + // requirePerfectMatch = options.RequirePerfectMatch; } else { subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; - requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + // requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; } foreach (var lang in subtitleDownloadLanguages) From 6b2b4849877f326792e41e234c6ff49cda566428 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 19 Apr 2021 10:22:32 +0100 Subject: [PATCH 003/167] Update SubtitleScheduledTask.cs --- MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index ab950040a7..12b23098ce 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -75,21 +75,18 @@ namespace MediaBrowser.Providers.MediaInfo string[] subtitleDownloadLanguages; bool skipIfEmbeddedSubtitlesPresent; bool skipIfAudioTrackMatches; - // bool requirePerfectMatch; if (libraryOptions.SubtitleDownloadLanguages == null) { subtitleDownloadLanguages = options.DownloadLanguages; skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; - // requirePerfectMatch = options.RequirePerfectMatch; } else { subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; - // requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; } foreach (var lang in subtitleDownloadLanguages) From 107412f2f2ae5b4b282ff533636661071b2d215d Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 19 Apr 2021 10:23:05 +0100 Subject: [PATCH 004/167] Update FFProbeVideoInfo.cs --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index d048d44b69..b077111975 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -111,11 +111,6 @@ namespace MediaBrowser.Providers.MediaInfo } } - // if (streamFileNames == null) - // { - // streamFileNames = Array.Empty(); - // } - mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); From a3a4689af22693b535e80b98624831866fda2a61 Mon Sep 17 00:00:00 2001 From: "Brian J. Murrell" Date: Thu, 22 Apr 2021 10:05:43 -0400 Subject: [PATCH 005/167] Allow to bind to priveleged ports (i.e. 80/443) Add "AmbientCapabilities=CAP_NET_BIND_SERVICE" to the "[Service]" section of the unit file to allow the server to bind to ports 80 and 443. Signed-off-by: Brian J. Murrell --- fedora/jellyfin.service | 1 + 1 file changed, 1 insertion(+) diff --git a/fedora/jellyfin.service b/fedora/jellyfin.service index b092ebf2f0..bde124a0fa 100644 --- a/fedora/jellyfin.service +++ b/fedora/jellyfin.service @@ -3,6 +3,7 @@ After=network.target Description=Jellyfin is a free software media system that puts you in control of managing and streaming your media. [Service] +AmbientCapabilities=CAP_NET_BIND_SERVICE EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin ExecStart=/usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} From d5b63092ed1b4b6ef4da2a5cdccec472aa1c06b3 Mon Sep 17 00:00:00 2001 From: Orry Verducci Date: Thu, 24 Jun 2021 17:51:35 +0100 Subject: [PATCH 006/167] Add H.264 MBAFF interlace check Use the codec time base to determine if a MBAFF coded H.264 file is interlaced. --- .../Probing/ProbeResultNormalizer.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index bbff5dacac..dd3faf5766 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -686,11 +686,6 @@ namespace MediaBrowser.MediaEncoding.Probing stream.IsAVC = false; } - if (!string.IsNullOrWhiteSpace(streamInfo.FieldOrder) && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase)) - { - stream.IsInterlaced = true; - } - // Filter out junk if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString) && !streamInfo.CodecTagString.Contains("[0]", StringComparison.OrdinalIgnoreCase)) { @@ -746,6 +741,19 @@ namespace MediaBrowser.MediaEncoding.Probing stream.AverageFrameRate = GetFrameRate(streamInfo.AverageFrameRate); stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate); + // Some interlaced H.264 files in mp4 containers using MBAFF coding aren't flagged as being interlaced by FFprobe, + // so for H.264 files we also check if the codec timebase duration is half the reported frame rate duration to + // determine if the file is interlaced + bool videoInterlaced = !string.IsNullOrWhiteSpace(streamInfo.FieldOrder) + && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase); + bool h264MbaffCoded = string.Equals(stream.Codec, "h264", StringComparison.OrdinalIgnoreCase) + && string.IsNullOrWhiteSpace(streamInfo.FieldOrder) + && 1f / (stream.AverageFrameRate * 2) == GetFrameRate(stream.CodecTimeBase); + if (videoInterlaced || h264MbaffCoded) + { + stream.IsInterlaced = true; + } + if (isAudio || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)) { From 9abe9e7e54cc454667ba2128b5d321631b5ece51 Mon Sep 17 00:00:00 2001 From: Orry Verducci Date: Sun, 31 Oct 2021 17:04:04 +0000 Subject: [PATCH 007/167] Add rounding to the time base check --- .../Probing/ProbeResultNormalizer.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index c9885ae76a..eb8850cd25 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -724,13 +724,17 @@ namespace MediaBrowser.MediaEncoding.Probing stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate); // Some interlaced H.264 files in mp4 containers using MBAFF coding aren't flagged as being interlaced by FFprobe, - // so for H.264 files we also check if the codec timebase duration is half the reported frame rate duration to - // determine if the file is interlaced + // so for H.264 files we also calculate the frame rate from the codec time base and check if it is double the reported + // frame rate (both rounded to the nearest integer) to determine if the file is interlaced + float roundedTimeBaseFPS = MathF.Round(1 / GetFrameRate(stream.CodecTimeBase) ?? 0); + float roundedDoubleFrameRate = MathF.Round(stream.AverageFrameRate * 2 ?? 0); + bool videoInterlaced = !string.IsNullOrWhiteSpace(streamInfo.FieldOrder) && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase); bool h264MbaffCoded = string.Equals(stream.Codec, "h264", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(streamInfo.FieldOrder) - && 1f / (stream.AverageFrameRate * 2) == GetFrameRate(stream.CodecTimeBase); + && roundedTimeBaseFPS == roundedDoubleFrameRate; + if (videoInterlaced || h264MbaffCoded) { stream.IsInterlaced = true; From d10de5b7f93511daa3cf239b4b4c34dc79000969 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 10 Nov 2021 23:25:01 +0100 Subject: [PATCH 008/167] Try to use Width and Height from ImageInfo to determine aspect ratio --- Emby.Server.Implementations/Dto/DtoService.cs | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 67ecd04e0e..4193d00184 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1398,41 +1398,33 @@ namespace Emby.Server.Implementations.Dto return null; } - ImageDimensions size; - - var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio(); - - if (defaultAspectRatio > 0) - { - return defaultAspectRatio; - } - if (!imageInfo.IsLocalFile) { - return null; + return item.GetDefaultPrimaryImageAspectRatio(); } - try - { - size = _imageProcessor.GetImageDimensions(item, imageInfo); + var width = imageInfo.Width; + var height = imageInfo.Height; - if (size.Width <= 0 || size.Height <= 0) + // Fallback to the image processor if the image info is somehow incorrect + if (width <= 0 || height <= 0) + { + try { - return null; + var size = _imageProcessor.GetImageDimensions(item, imageInfo); + width = size.Width; + height = size.Height; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path); + return item.GetDefaultPrimaryImageAspectRatio(); } } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path); - return null; - } - - var width = size.Width; - var height = size.Height; if (width <= 0 || height <= 0) { - return null; + return item.GetDefaultPrimaryImageAspectRatio(); } return (double)width / height; From 0415d1ccef19c48a2eaa3c545648ce657f95cfb8 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 10 Nov 2021 23:29:41 +0100 Subject: [PATCH 009/167] Reduce indentation --- Emby.Server.Implementations/Dto/DtoService.cs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 4193d00184..f23f4a13f3 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1406,28 +1406,29 @@ namespace Emby.Server.Implementations.Dto var width = imageInfo.Width; var height = imageInfo.Height; + if (width > 0 && height > 0) + { + return (double)width / height; + } + // Fallback to the image processor if the image info is somehow incorrect - if (width <= 0 || height <= 0) + try { - try - { - var size = _imageProcessor.GetImageDimensions(item, imageInfo); - width = size.Width; - height = size.Height; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path); - return item.GetDefaultPrimaryImageAspectRatio(); - } + var size = _imageProcessor.GetImageDimensions(item, imageInfo); + width = size.Width; + height = size.Height; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path); } - if (width <= 0 || height <= 0) + if (width > 0 && height > 0) { - return item.GetDefaultPrimaryImageAspectRatio(); + return (double)width / height; } - return (double)width / height; + return item.GetDefaultPrimaryImageAspectRatio(); } } } From 5d19c26d5966db3ff48c73f409290509815cd89a Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 10 Nov 2021 23:46:56 +0100 Subject: [PATCH 010/167] Simplify --- Emby.Server.Implementations/Dto/DtoService.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index f23f4a13f3..ab5d452790 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1403,31 +1403,21 @@ namespace Emby.Server.Implementations.Dto return item.GetDefaultPrimaryImageAspectRatio(); } - var width = imageInfo.Width; - var height = imageInfo.Height; - - if (width > 0 && height > 0) - { - return (double)width / height; - } - - // Fallback to the image processor if the image info is somehow incorrect try { var size = _imageProcessor.GetImageDimensions(item, imageInfo); - width = size.Width; - height = size.Height; + var width = size.Width; + var height = size.Height; + if (width > 0 && height > 0) + { + return (double)width / height; + } } catch (Exception ex) { _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path); } - if (width > 0 && height > 0) - { - return (double)width / height; - } - return item.GetDefaultPrimaryImageAspectRatio(); } } From 3734c95fd498414c2fcc3a8c46f5cddd14515258 Mon Sep 17 00:00:00 2001 From: Yordany Rodriguez Esquirol <35172997+yresquirol@users.noreply.github.com> Date: Thu, 18 Nov 2021 15:23:47 -0500 Subject: [PATCH 011/167] Related media according to genre --- Jellyfin.Api/Controllers/LibraryController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 0be853ca4f..b3c6fac7c8 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -739,6 +739,7 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { + Genres = item.Genres, // Passing items' genres to obtain a more accurate list of its related media Limit = limit, IncludeItemTypes = includeItemTypes.ToArray(), SimilarTo = item, From 6d3b1296661a486d511fa12163456f96cfa3fcc7 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Fri, 19 Nov 2021 22:02:26 +0100 Subject: [PATCH 012/167] Add image scaling options for tmdb --- .../Tmdb/Configuration/PluginConfiguration.cs | 41 +++++++++ .../Plugins/Tmdb/Configuration/config.html | 41 ++++++++- .../Plugins/Tmdb/TmdbClientManager.cs | 91 +++++++++++++++++-- 3 files changed, 162 insertions(+), 11 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs index 9a78a75362..b043da76ca 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using MediaBrowser.Model.Plugins; namespace MediaBrowser.Providers.Plugins.Tmdb @@ -26,5 +27,45 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// Gets or sets a value indicating the maximum number of cast members to fetch for an item. /// public int MaxCastMembers { get; set; } = 15; + + /// + /// Gets or sets a value indicating the poster image size to fetch. + /// + public string? PosterSize { get; set; } + + /// + /// Gets or sets the available options for poster size. + /// + public List PosterSizeOptions { get; set; } = new List(); + + /// + /// Gets or sets a value indicating the backdrop image size to fetch. + /// + public string? BackdropSize { get; set; } + + /// + /// Gets or sets the available options for backdrop size. + /// + public List BackdropSizeOptions { get; set; } = new List(); + + /// + /// Gets or sets a value indicating the profile image size to fetch. + /// + public string? ProfileSize { get; set; } + + /// + /// Gets or sets the available options for profile size. + /// + public List ProfileSizeOptions { get; set; } = new List(); + + /// + /// Gets or sets a value indicating the still image size to fetch. + /// + public string? StillSize { get; set; } + + /// + /// Gets or sets the available options for still size. + /// + public List StillSizeOptions { get; set; } = new List(); } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html index 12b4c7ca4e..d376df96cc 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -24,7 +24,22 @@
The maximum number of cast members to fetch for an item.
-
+
+

Image Scaling

+

If size options are not populated then refresh metadata for any item from TMDb and reload this page.

+
+ +
+
+ +
+
+ +
+
+ +
+
@@ -51,6 +66,26 @@ cancelable: false })); + var sizeOptionsGenerator = function (size) { + return ''; + } + + var selPosterSize = document.querySelector('#selectPosterSize'); + selPosterSize.innerHTML = config.PosterSizeOptions.map(sizeOptionsGenerator); + selPosterSize.value = config.PosterSize; + + var selBackdropSize = document.querySelector('#selectBackdropSize'); + selBackdropSize.innerHTML = config.BackdropSizeOptions.map(sizeOptionsGenerator); + selBackdropSize.value = config.BackdropSize; + + var selProfileSize = document.querySelector('#selectProfileSize'); + selProfileSize.innerHTML = config.ProfileSizeOptions.map(sizeOptionsGenerator); + selProfileSize.value = config.ProfileSize; + + var selStillSize = document.querySelector('#selectStillSize'); + selStillSize.innerHTML = config.StillSizeOptions.map(sizeOptionsGenerator); + selStillSize.value = config.StillSize; + Dashboard.hideLoadingMsg(); }); }); @@ -65,6 +100,10 @@ config.ExcludeTagsSeries = document.querySelector('#excludeTagsSeries').checked; config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked; config.MaxCastMembers = document.querySelector('#maxCastMembers').value; + config.PosterSize = document.querySelector('#selectPosterSize').value; + config.BackdropSize = document.querySelector('#selectBackdropSize').value; + config.ProfileSize = document.querySelector('#selectProfileSize').value; + config.StillSize = document.querySelector('#selectStillSize').value; ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); }); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index cb644c8ca1..c082a6cc4b 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Dto; @@ -508,7 +509,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The absolute URL. public string GetPosterUrl(string posterPath) { - return GetUrl(_tmDbClient.Config.Images.PosterSizes[^1], posterPath); + return GetUrl(Plugin.Instance.Configuration.PosterSize, posterPath); } /// @@ -518,7 +519,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The absolute URL. public string GetProfileUrl(string actorProfilePath) { - return GetUrl(_tmDbClient.Config.Images.ProfileSizes[^1], actorProfilePath); + return GetUrl(Plugin.Instance.Configuration.ProfileSize, actorProfilePath); } /// @@ -529,7 +530,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The collection to add the remote images into. public void ConvertPostersToRemoteImageInfo(List images, string requestLanguage, List results) { - ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.PosterSizes[^1], ImageType.Primary, requestLanguage, results); + ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.PosterSize, ImageType.Primary, requestLanguage, results); } /// @@ -540,7 +541,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The collection to add the remote images into. public void ConvertBackdropsToRemoteImageInfo(List images, string requestLanguage, List results) { - ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.BackdropSizes[^1], ImageType.Backdrop, requestLanguage, results); + ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.BackdropSize, ImageType.Backdrop, requestLanguage, results); } /// @@ -551,7 +552,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The collection to add the remote images into. public void ConvertProfilesToRemoteImageInfo(List images, string requestLanguage, List results) { - ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.ProfileSizes[^1], ImageType.Primary, requestLanguage, results); + ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.ProfileSize, ImageType.Primary, requestLanguage, results); } /// @@ -562,7 +563,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The collection to add the remote images into. public void ConvertStillsToRemoteImageInfo(List images, string requestLanguage, List results) { - ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.StillSizes[^1], ImageType.Primary, requestLanguage, results); + ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.StillSize, ImageType.Primary, requestLanguage, results); } /// @@ -575,16 +576,51 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The collection to add the remote images into. private void ConvertToRemoteImageInfo(List images, string size, ImageType type, string requestLanguage, List results) { + int? targetHeight = null; + int? targetWidth = null; + var match = Regex.Match(size, @"(?[hw])(?[0-9]+)"); + if (match.Success) + { + var targetSize = int.Parse(match.Groups["size"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture); + if (string.Equals(match.Groups["dimension"].Value, "h", StringComparison.OrdinalIgnoreCase)) + { + targetHeight = targetSize; + } + else + { + targetWidth = targetSize; + } + } + for (var i = 0; i < images.Count; i++) { var image = images[i]; + + int width; + int height; + if (targetHeight.HasValue) + { + width = (int)Math.Round((double)targetHeight.Value / image.Height * image.Width); + height = targetHeight.Value; + } + else if (targetWidth.HasValue) + { + height = (int)Math.Round((double)targetWidth.Value / image.Width * image.Height); + width = targetWidth.Value; + } + else + { + width = image.Width; + height = image.Height; + } + results.Add(new RemoteImageInfo { Url = GetUrl(size, image.FilePath), CommunityRating = image.VoteAverage, VoteCount = image.VoteCount, - Width = image.Width, - Height = image.Height, + Width = width, + Height = height, Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, requestLanguage), ProviderName = TmdbUtils.ProviderName, Type = type, @@ -593,9 +629,44 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } } - private Task EnsureClientConfigAsync() + private async Task EnsureClientConfigAsync() { - return !_tmDbClient.HasConfig ? _tmDbClient.GetConfigAsync() : Task.CompletedTask; + if (!_tmDbClient.HasConfig) + { + var config = await _tmDbClient.GetConfigAsync().ConfigureAwait(false); + ValidatePreferences(config); + } + } + + private static void ValidatePreferences(TMDbConfig config) + { + var imageConfig = config.Images; + + var pluginConfig = Plugin.Instance.Configuration; + + pluginConfig.PosterSizeOptions = imageConfig.PosterSizes; + if (!pluginConfig.PosterSizeOptions.Contains(pluginConfig.PosterSize)) + { + pluginConfig.PosterSize = pluginConfig.PosterSizeOptions[^1]; + } + + pluginConfig.BackdropSizeOptions = imageConfig.BackdropSizes; + if (!pluginConfig.BackdropSizeOptions.Contains(pluginConfig.BackdropSize)) + { + pluginConfig.BackdropSize = pluginConfig.BackdropSizeOptions[^1]; + } + + pluginConfig.ProfileSizeOptions = imageConfig.ProfileSizes; + if (!pluginConfig.ProfileSizeOptions.Contains(pluginConfig.ProfileSize)) + { + pluginConfig.ProfileSize = pluginConfig.ProfileSizeOptions[^1]; + } + + pluginConfig.StillSizeOptions = imageConfig.StillSizes; + if (!pluginConfig.StillSizeOptions.Contains(pluginConfig.StillSize)) + { + pluginConfig.StillSize = pluginConfig.StillSizeOptions[^1]; + } } /// From 0af5e600946231df73f1783330ad8cd0bbbed2ee Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 22 Nov 2021 21:08:07 +0100 Subject: [PATCH 013/167] Address review comments Store null instead of calculating scaled image sizes. Add endpoint to provide TMDb image size options. --- .../Plugins/Tmdb/Api/TmdbController.cs | 41 +++++++++++ .../Tmdb/Configuration/PluginConfiguration.cs | 21 ------ .../Plugins/Tmdb/Configuration/config.html | 65 +++++++++++------ .../Plugins/Tmdb/TmdbClientManager.cs | 71 ++++++------------- 4 files changed, 107 insertions(+), 91 deletions(-) create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs new file mode 100644 index 0000000000..0bab7c3cad --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs @@ -0,0 +1,41 @@ +using System.Net.Mime; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using TMDbLib.Objects.General; + +namespace MediaBrowser.Providers.Plugins.Tmdb.Api +{ + /// + /// The TMDb api controller. + /// + [ApiController] + [Authorize(Policy = "DefaultAuthorization")] + [Route("[controller]")] + [Produces(MediaTypeNames.Application.Json)] + public class TmdbController : ControllerBase + { + private readonly TmdbClientManager _tmdbClientManager; + + /// + /// Initializes a new instance of the class. + /// + /// The TMDb client manager. + public TmdbController(TmdbClientManager tmdbClientManager) + { + _tmdbClientManager = tmdbClientManager; + } + + /// + /// Gets the TMDb image configuration options. + /// + /// The image portion of the TMDb client configuration. + [HttpGet("ClientConfiguration")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task TmdbClientConfiguration() + { + return (await _tmdbClientManager.GetClientConfiguration().ConfigureAwait(false)).Images; + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs index b043da76ca..dec7961484 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using MediaBrowser.Model.Plugins; namespace MediaBrowser.Providers.Plugins.Tmdb @@ -33,39 +32,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// public string? PosterSize { get; set; } - /// - /// Gets or sets the available options for poster size. - /// - public List PosterSizeOptions { get; set; } = new List(); - /// /// Gets or sets a value indicating the backdrop image size to fetch. /// public string? BackdropSize { get; set; } - /// - /// Gets or sets the available options for backdrop size. - /// - public List BackdropSizeOptions { get; set; } = new List(); - /// /// Gets or sets a value indicating the profile image size to fetch. /// public string? ProfileSize { get; set; } - /// - /// Gets or sets the available options for profile size. - /// - public List ProfileSizeOptions { get; set; } = new List(); - /// /// Gets or sets a value indicating the still image size to fetch. /// public string? StillSize { get; set; } - - /// - /// Gets or sets the available options for still size. - /// - public List StillSizeOptions { get; set; } = new List(); } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html index d376df96cc..52693795b5 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -26,7 +26,6 @@

Image Scaling

-

If size options are not populated then refresh metadata for any item from TMDb and reload this page.

@@ -54,6 +53,47 @@ document.querySelector('.configPage') .addEventListener('pageshow', function () { Dashboard.showLoadingMsg(); + + var clientConfig, pluginConfig; + var configureImageScaling = function() { + if (clientConfig === null || pluginConfig === null) { + return; + } + + var sizeOptionsGenerator = function (size) { + return ''; + } + + var selPosterSize = document.querySelector('#selectPosterSize'); + selPosterSize.innerHTML = clientConfig.PosterSizes.map(sizeOptionsGenerator); + selPosterSize.value = pluginConfig.PosterSize; + + var selBackdropSize = document.querySelector('#selectBackdropSize'); + selBackdropSize.innerHTML = clientConfig.BackdropSizes.map(sizeOptionsGenerator); + selBackdropSize.value = pluginConfig.BackdropSize; + + var selProfileSize = document.querySelector('#selectProfileSize'); + selProfileSize.innerHTML = clientConfig.ProfileSizes.map(sizeOptionsGenerator); + selProfileSize.value = pluginConfig.ProfileSize; + + var selStillSize = document.querySelector('#selectStillSize'); + selStillSize.innerHTML = clientConfig.StillSizes.map(sizeOptionsGenerator); + selStillSize.value = pluginConfig.StillSize; + + Dashboard.hideLoadingMsg(); + } + + const request = { + url: ApiClient.getUrl('tmdb/ClientConfiguration'), + dataType: 'json', + type: 'GET', + headers: { accept: 'application/json' } + } + ApiClient.fetch(request).then(function (config) { + clientConfig = config; + configureImageScaling(); + }); + ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { document.querySelector('#includeAdult').checked = config.IncludeAdult; document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries; @@ -66,27 +106,8 @@ cancelable: false })); - var sizeOptionsGenerator = function (size) { - return ''; - } - - var selPosterSize = document.querySelector('#selectPosterSize'); - selPosterSize.innerHTML = config.PosterSizeOptions.map(sizeOptionsGenerator); - selPosterSize.value = config.PosterSize; - - var selBackdropSize = document.querySelector('#selectBackdropSize'); - selBackdropSize.innerHTML = config.BackdropSizeOptions.map(sizeOptionsGenerator); - selBackdropSize.value = config.BackdropSize; - - var selProfileSize = document.querySelector('#selectProfileSize'); - selProfileSize.innerHTML = config.ProfileSizeOptions.map(sizeOptionsGenerator); - selProfileSize.value = config.ProfileSize; - - var selStillSize = document.querySelector('#selectStillSize'); - selStillSize.innerHTML = config.StillSizeOptions.map(sizeOptionsGenerator); - selStillSize.value = config.StillSize; - - Dashboard.hideLoadingMsg(); + pluginConfig = config; + configureImageScaling(); }); }); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index c082a6cc4b..9a3af1f4af 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Dto; @@ -576,51 +575,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The collection to add the remote images into. private void ConvertToRemoteImageInfo(List images, string size, ImageType type, string requestLanguage, List results) { - int? targetHeight = null; - int? targetWidth = null; - var match = Regex.Match(size, @"(?[hw])(?[0-9]+)"); - if (match.Success) - { - var targetSize = int.Parse(match.Groups["size"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture); - if (string.Equals(match.Groups["dimension"].Value, "h", StringComparison.OrdinalIgnoreCase)) - { - targetHeight = targetSize; - } - else - { - targetWidth = targetSize; - } - } + // sizes provided are for original resolution, don't store them when downloading scaled images + var scaleImage = !string.Equals(size, "original", StringComparison.OrdinalIgnoreCase); for (var i = 0; i < images.Count; i++) { var image = images[i]; - int width; - int height; - if (targetHeight.HasValue) - { - width = (int)Math.Round((double)targetHeight.Value / image.Height * image.Width); - height = targetHeight.Value; - } - else if (targetWidth.HasValue) - { - height = (int)Math.Round((double)targetWidth.Value / image.Width * image.Height); - width = targetWidth.Value; - } - else - { - width = image.Width; - height = image.Height; - } - results.Add(new RemoteImageInfo { Url = GetUrl(size, image.FilePath), CommunityRating = image.VoteAverage, VoteCount = image.VoteCount, - Width = width, - Height = height, + Width = scaleImage ? null : image.Width, + Height = scaleImage ? null : image.Height, Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, requestLanguage), ProviderName = TmdbUtils.ProviderName, Type = type, @@ -644,31 +612,38 @@ namespace MediaBrowser.Providers.Plugins.Tmdb var pluginConfig = Plugin.Instance.Configuration; - pluginConfig.PosterSizeOptions = imageConfig.PosterSizes; - if (!pluginConfig.PosterSizeOptions.Contains(pluginConfig.PosterSize)) + if (!imageConfig.PosterSizes.Contains(pluginConfig.PosterSize)) { - pluginConfig.PosterSize = pluginConfig.PosterSizeOptions[^1]; + pluginConfig.PosterSize = imageConfig.PosterSizes[^1]; } - pluginConfig.BackdropSizeOptions = imageConfig.BackdropSizes; - if (!pluginConfig.BackdropSizeOptions.Contains(pluginConfig.BackdropSize)) + if (!imageConfig.BackdropSizes.Contains(pluginConfig.BackdropSize)) { - pluginConfig.BackdropSize = pluginConfig.BackdropSizeOptions[^1]; + pluginConfig.BackdropSize = imageConfig.BackdropSizes[^1]; } - pluginConfig.ProfileSizeOptions = imageConfig.ProfileSizes; - if (!pluginConfig.ProfileSizeOptions.Contains(pluginConfig.ProfileSize)) + if (!imageConfig.ProfileSizes.Contains(pluginConfig.ProfileSize)) { - pluginConfig.ProfileSize = pluginConfig.ProfileSizeOptions[^1]; + pluginConfig.ProfileSize = imageConfig.ProfileSizes[^1]; } - pluginConfig.StillSizeOptions = imageConfig.StillSizes; - if (!pluginConfig.StillSizeOptions.Contains(pluginConfig.StillSize)) + if (!imageConfig.StillSizes.Contains(pluginConfig.StillSize)) { - pluginConfig.StillSize = pluginConfig.StillSizeOptions[^1]; + pluginConfig.StillSize = imageConfig.StillSizes[^1]; } } + /// + /// Gets the configuration. + /// + /// The configuration. + public async Task GetClientConfiguration() + { + await EnsureClientConfigAsync().ConfigureAwait(false); + + return _tmDbClient.Config; + } + /// public void Dispose() { From 69df004b9f62cb65986607d659fde0dcc74004ab Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 24 Nov 2021 13:00:12 +0100 Subject: [PATCH 014/167] Migrate network configuration safely --- .../ApplicationHost.cs | 18 -- Jellyfin.Api/Helpers/ClassMigrationHelper.cs | 71 ------ Jellyfin.Server/Migrations/MigrationRunner.cs | 63 ++++- .../CreateNetworkConfiguration.cs | 140 +++++++++++ Jellyfin.Server/Program.cs | 1 + .../Configuration/ServerConfiguration.cs | 222 ------------------ 6 files changed, 196 insertions(+), 319 deletions(-) delete mode 100644 Jellyfin.Api/Helpers/ClassMigrationHelper.cs create mode 100644 Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 903c311334..8892f7f40f 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -313,22 +313,6 @@ namespace Emby.Server.Implementations ? Environment.MachineName : ConfigurationManager.Configuration.ServerName; - /// - /// Temporary function to migration network settings out of system.xml and into network.xml. - /// TODO: remove at the point when a fixed migration path has been decided upon. - /// - private void MigrateNetworkConfiguration() - { - string path = Path.Combine(ConfigurationManager.CommonApplicationPaths.ConfigurationDirectoryPath, "network.xml"); - if (!File.Exists(path)) - { - var networkSettings = new NetworkConfiguration(); - ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings); - _xmlSerializer.SerializeToFile(networkSettings, path); - Logger.LogDebug("Successfully migrated network settings."); - } - } - public string ExpandVirtualPath(string path) { var appPaths = ApplicationPaths; @@ -513,8 +497,6 @@ namespace Emby.Server.Implementations ConfigurationManager.AddParts(GetExports()); - // Have to migrate settings here as migration subsystem not yet initialised. - MigrateNetworkConfiguration(); NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger()); // Initialize runtime stat collection diff --git a/Jellyfin.Api/Helpers/ClassMigrationHelper.cs b/Jellyfin.Api/Helpers/ClassMigrationHelper.cs deleted file mode 100644 index 76fb27bcc3..0000000000 --- a/Jellyfin.Api/Helpers/ClassMigrationHelper.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Reflection; - -namespace Jellyfin.Api.Helpers -{ - /// - /// A static class for copying matching properties from one object to another. - /// TODO: remove at the point when a fixed migration path has been decided upon. - /// - public static class ClassMigrationHelper - { - /// - /// Extension for 'Object' that copies the properties to a destination object. - /// - /// The source. - /// The destination. - public static void CopyProperties(this object source, object destination) - { - // If any this null throw an exception. - if (source == null || destination == null) - { - throw new ArgumentException("Source or/and Destination Objects are null"); - } - - // Getting the Types of the objects. - Type typeDest = destination.GetType(); - Type typeSrc = source.GetType(); - - // Iterate the Properties of the source instance and populate them from their destination counterparts. - PropertyInfo[] srcProps = typeSrc.GetProperties(); - foreach (PropertyInfo srcProp in srcProps) - { - if (!srcProp.CanRead) - { - continue; - } - - var targetProperty = typeDest.GetProperty(srcProp.Name); - if (targetProperty == null) - { - continue; - } - - if (!targetProperty.CanWrite) - { - continue; - } - - var obj = targetProperty.GetSetMethod(true); - if (obj != null && obj.IsPrivate) - { - continue; - } - - var target = targetProperty.GetSetMethod(); - if (target != null && (target.Attributes & MethodAttributes.Static) != 0) - { - continue; - } - - if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)) - { - continue; - } - - // Passed all tests, lets set the value. - targetProperty.SetValue(destination, srcProp.GetValue(source, null), null); - } - } - } -} diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 7365c8dbc1..11b6c4912b 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -1,6 +1,11 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using Emby.Server.Implementations; +using Emby.Server.Implementations.Serialization; using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -11,6 +16,14 @@ namespace Jellyfin.Server.Migrations ///
public sealed class MigrationRunner { + /// + /// The list of known pre-startup migrations, in order of applicability. + /// + private static readonly Type[] _preStartupMigrationTypes = + { + typeof(PreStartupRoutines.CreateNetworkConfiguration) + }; + /// /// The list of known migrations, in order of applicability. /// @@ -29,6 +42,7 @@ namespace Jellyfin.Server.Migrations typeof(Routines.MigrateAuthenticationDb) }; + /// /// Run all needed migrations. /// @@ -41,17 +55,50 @@ namespace Jellyfin.Server.Migrations .Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m)) .OfType() .ToArray(); - var migrationOptions = host.ConfigurationManager.GetConfiguration(MigrationsListStore.StoreKey); - if (!host.ConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0) + var migrationOptions = host.ConfigurationManager.GetConfiguration(MigrationsListStore.StoreKey); + HandleStartupWizardCondition(migrations, migrationOptions, host.ConfigurationManager.Configuration.IsStartupWizardCompleted, logger); + PerformMigrations(migrations, migrationOptions, options => host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, options), logger); + } + + /// + /// Run all needed pre-startup migrations. + /// + /// Application paths. + /// Factory for making the logger. + public static void RunPreStartup(ServerApplicationPaths appPaths, ILoggerFactory loggerFactory) + { + var logger = loggerFactory.CreateLogger(); + var migrations = _preStartupMigrationTypes + .Select(m => Activator.CreateInstance(m, appPaths, loggerFactory)) + .OfType() + .ToArray(); + + var xmlSerializer = new MyXmlSerializer(); + var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, MigrationsListStore.StoreKey.ToLowerInvariant() + ".xml"); + var migrationOptions = (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)!; + + // We have to deserialize it manually since the configuration manager may overwrite it + var serverConfig = (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)!; + HandleStartupWizardCondition(migrations, migrationOptions, serverConfig.IsStartupWizardCompleted, logger); + PerformMigrations(migrations, migrationOptions, options => xmlSerializer.SerializeToFile(options, migrationConfigPath), logger); + } + + private static void HandleStartupWizardCondition(IEnumerable migrations, MigrationOptions migrationOptions, bool isStartWizardCompleted, ILogger logger) + { + if (isStartWizardCompleted || migrationOptions.Applied.Count != 0) { - // If startup wizard is not finished, this is a fresh install. - // Don't run any migrations, just mark all of them as applied. - logger.LogInformation("Marking all known migrations as applied because this is a fresh install"); - migrationOptions.Applied.AddRange(migrations.Where(m => !m.PerformOnNewInstall).Select(m => (m.Id, m.Name))); - host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + return; } + // If startup wizard is not finished, this is a fresh install. + var onlyOldInstalls = migrations.Where(m => !m.PerformOnNewInstall).ToArray(); + logger.LogInformation("Marking following migrations as applied because this is a fresh install: {@OnlyOldInstalls}", onlyOldInstalls.Select(m => m.Name)); + migrationOptions.Applied.AddRange(onlyOldInstalls.Select(m => (m.Id, m.Name))); + } + + private static void PerformMigrations(IMigrationRoutine[] migrations, MigrationOptions migrationOptions, Action saveConfiguration, ILogger logger) + { var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet(); for (var i = 0; i < migrations.Length; i++) @@ -78,7 +125,7 @@ namespace Jellyfin.Server.Migrations // Mark the migration as completed logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); migrationOptions.Applied.Add((migrationRoutine.Id, migrationRoutine.Name)); - host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + saveConfiguration(migrationOptions); logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); } } diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs new file mode 100644 index 0000000000..98c845dc3f --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -0,0 +1,140 @@ +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Xml; +using System.Xml.Serialization; +using Emby.Server.Implementations; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.PreStartupRoutines; + +/// +public class CreateNetworkConfiguration : IMigrationRoutine +{ + private readonly ServerApplicationPaths _applicationPaths; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// An instance of . + /// An instance of the interface. + public CreateNetworkConfiguration(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory) + { + _applicationPaths = applicationPaths; + _logger = loggerFactory.CreateLogger(); + } + + /// + public Guid Id => Guid.Parse("9B354818-94D5-4B68-AC49-E35CB85F9D84"); + + /// + public string Name => nameof(CreateNetworkConfiguration); + + /// + public bool PerformOnNewInstall => false; + + /// + public void Perform() + { + string path = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "network.xml"); + if (File.Exists(path)) + { + _logger.LogDebug("Network configuration file already exists, skipping"); + return; + } + + var serverConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("ServerConfiguration")); + using var xmlReader = XmlReader.Create(_applicationPaths.SystemConfigurationFilePath); + var networkSettings = serverConfigSerializer.Deserialize(xmlReader); + + var networkConfigSerializer = new XmlSerializer(typeof(OldNetworkConfiguration), new XmlRootAttribute("NetworkConfiguration")); + var xmlWriterSettings = new XmlWriterSettings { Indent = true }; + using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); + networkConfigSerializer.Serialize(xmlWriter, networkSettings); + } + +#pragma warning disable CS1591 + public sealed class OldNetworkConfiguration + { + public const int DefaultHttpPort = 8096; + + public const int DefaultHttpsPort = 8920; + + private string _baseUrl = string.Empty; + + public bool RequireHttps { get; set; } + + public string CertificatePath { get; set; } = string.Empty; + + public string CertificatePassword { get; set; } = string.Empty; + + public string BaseUrl + { + get => _baseUrl; + set + { + // Normalize the start of the string + if (string.IsNullOrWhiteSpace(value)) + { + // If baseUrl is empty, set an empty prefix string + _baseUrl = string.Empty; + return; + } + + if (value[0] != '/') + { + // If baseUrl was not configured with a leading slash, append one for consistency + value = "/" + value; + } + + // Normalize the end of the string + if (value[^1] == '/') + { + // If baseUrl was configured with a trailing slash, remove it for consistency + value = value.Remove(value.Length - 1); + } + + _baseUrl = value; + } + } + + public int PublicHttpsPort { get; set; } = DefaultHttpsPort; + + public int HttpServerPortNumber { get; set; } = DefaultHttpPort; + + public int HttpsPortNumber { get; set; } = DefaultHttpsPort; + + public bool EnableHttps { get; set; } + + public int PublicPort { get; set; } = DefaultHttpPort; + + public bool EnableIPV6 { get; set; } + + [DataMember(Name = "EnableIPV4")] + public bool EnableIPV4 { get; set; } = true; + + public bool IgnoreVirtualInterfaces { get; set; } = true; + + public string VirtualInterfaceNames { get; set; } = "vEthernet*"; + + public bool TrustAllIP6Interfaces { get; set; } + + public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty(); + + public string[] RemoteIPFilter { get; set; } = Array.Empty(); + + public bool IsRemoteIPFilterBlacklist { get; set; } + + public bool EnableUPnP { get; set; } + + public bool EnableRemoteAccess { get; set; } = true; + + public string[] LocalNetworkSubnets { get; set; } = Array.Empty(); + + public string[] LocalNetworkAddresses { get; set; } = Array.Empty(); + + public string[] KnownProxies { get; set; } = Array.Empty(); + } +#pragma warning restore CS1591 +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 7f158aebb4..f40526e223 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -175,6 +175,7 @@ namespace Jellyfin.Server } PerformStaticInitialization(); + Migrations.MigrationRunner.RunPreStartup(appPaths, _loggerFactory); var appHost = new CoreAppHost( appPaths, diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 0ab721b77d..46e61ee1a2 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -13,18 +13,6 @@ namespace MediaBrowser.Model.Configuration ///
public class ServerConfiguration : BaseApplicationConfiguration { - /// - /// The default value for . - /// - public const int DefaultHttpPort = 8096; - - /// - /// The default value for and . - /// - public const int DefaultHttpsPort = 8920; - - private string _baseUrl = string.Empty; - /// /// Initializes a new instance of the class. /// @@ -75,149 +63,13 @@ namespace MediaBrowser.Model.Configuration }; } - /// - /// Gets or sets a value indicating whether to enable automatic port forwarding. - /// - public bool EnableUPnP { get; set; } = false; - /// /// Gets or sets a value indicating whether to enable prometheus metrics exporting. /// public bool EnableMetrics { get; set; } = false; - /// - /// Gets or sets the public mapped port. - /// - /// The public mapped port. - public int PublicPort { get; set; } = DefaultHttpPort; - - /// - /// Gets or sets a value indicating whether the http port should be mapped as part of UPnP automatic port forwarding. - /// - public bool UPnPCreateHttpPortMap { get; set; } = false; - - /// - /// Gets or sets client udp port range. - /// - public string UDPPortRange { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether IPV6 capability is enabled. - /// - public bool EnableIPV6 { get; set; } = false; - - /// - /// Gets or sets a value indicating whether IPV4 capability is enabled. - /// - public bool EnableIPV4 { get; set; } = true; - - /// - /// Gets or sets a value indicating whether detailed ssdp logs are sent to the console/log. - /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to work. - /// - public bool EnableSSDPTracing { get; set; } = false; - - /// - /// Gets or sets a value indicating whether an IP address is to be used to filter the detailed ssdp logs that are being sent to the console/log. - /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work. - /// - public string SSDPTracingFilter { get; set; } = string.Empty; - - /// - /// Gets or sets the number of times SSDP UDP messages are sent. - /// - public int UDPSendCount { get; set; } = 2; - - /// - /// Gets or sets the delay between each groups of SSDP messages (in ms). - /// - public int UDPSendDelay { get; set; } = 100; - - /// - /// Gets or sets a value indicating whether address names that match should be Ignore for the purposes of binding. - /// - public bool IgnoreVirtualInterfaces { get; set; } = true; - - /// - /// Gets or sets a value indicating the interfaces that should be ignored. The list can be comma separated. . - /// - public string VirtualInterfaceNames { get; set; } = "vEthernet*"; - - /// - /// Gets or sets the time (in seconds) between the pings of SSDP gateway monitor. - /// - public int GatewayMonitorPeriod { get; set; } = 60; - - /// - /// Gets a value indicating whether multi-socket binding is available. - /// - public bool EnableMultiSocketBinding { get; } = true; - - /// - /// Gets or sets a value indicating whether all IPv6 interfaces should be treated as on the internal network. - /// Depending on the address range implemented ULA ranges might not be used. - /// - public bool TrustAllIP6Interfaces { get; set; } = false; - - /// - /// Gets or sets the ports that HDHomerun uses. - /// - public string HDHomerunPortRange { get; set; } = string.Empty; - - /// - /// Gets or sets PublishedServerUri to advertise for specific subnets. - /// - public string[] PublishedServerUriBySubnet { get; set; } = Array.Empty(); - - /// - /// Gets or sets a value indicating whether Autodiscovery tracing is enabled. - /// - public bool AutoDiscoveryTracing { get; set; } = false; - - /// - /// Gets or sets a value indicating whether Autodiscovery is enabled. - /// - public bool AutoDiscovery { get; set; } = true; - - /// - /// Gets or sets the public HTTPS port. - /// - /// The public HTTPS port. - public int PublicHttpsPort { get; set; } = DefaultHttpsPort; - - /// - /// Gets or sets the HTTP server port number. - /// - /// The HTTP server port number. - public int HttpServerPortNumber { get; set; } = DefaultHttpPort; - - /// - /// Gets or sets the HTTPS server port number. - /// - /// The HTTPS server port number. - public int HttpsPortNumber { get; set; } = DefaultHttpsPort; - - /// - /// Gets or sets a value indicating whether to use HTTPS. - /// - /// - /// In order for HTTPS to be used, in addition to setting this to true, valid values must also be - /// provided for and . - /// - public bool EnableHttps { get; set; } = false; - public bool EnableNormalizedItemByNameIds { get; set; } = true; - /// - /// Gets or sets the filesystem path of an X.509 certificate to use for SSL. - /// - public string CertificatePath { get; set; } = string.Empty; - - /// - /// Gets or sets the password required to access the X.509 certificate data in the file specified by . - /// - public string CertificatePassword { get; set; } = string.Empty; - /// /// Gets or sets a value indicating whether this instance is port authorized. /// @@ -229,11 +81,6 @@ namespace MediaBrowser.Model.Configuration ///
public bool QuickConnectAvailable { get; set; } = false; - /// - /// Gets or sets a value indicating whether access outside of the LAN is permitted. - /// - public bool EnableRemoteAccess { get; set; } = true; - /// /// Gets or sets a value indicating whether [enable case sensitive item ids]. /// @@ -318,13 +165,6 @@ namespace MediaBrowser.Model.Configuration /// The file watcher delay. public int LibraryMonitorDelay { get; set; } = 60; - /// - /// Gets or sets a value indicating whether [enable dashboard response caching]. - /// Allows potential contributors without visual studio to modify production dashboard code and test changes. - /// - /// true if [enable dashboard response caching]; otherwise, false. - public bool EnableDashboardResponseCaching { get; set; } = true; - /// /// Gets or sets the image saving convention. /// @@ -337,36 +177,6 @@ namespace MediaBrowser.Model.Configuration public string ServerName { get; set; } = string.Empty; - public string BaseUrl - { - get => _baseUrl; - set - { - // Normalize the start of the string - if (string.IsNullOrWhiteSpace(value)) - { - // If baseUrl is empty, set an empty prefix string - _baseUrl = string.Empty; - return; - } - - if (value[0] != '/') - { - // If baseUrl was not configured with a leading slash, append one for consistency - value = "/" + value; - } - - // Normalize the end of the string - if (value[value.Length - 1] == '/') - { - // If baseUrl was configured with a trailing slash, remove it for consistency - value = value.Remove(value.Length - 1); - } - - _baseUrl = value; - } - } - public string UICulture { get; set; } = "en-US"; public bool SaveMetadataHidden { get; set; } = false; @@ -381,43 +191,16 @@ namespace MediaBrowser.Model.Configuration public bool DisplaySpecialsWithinSeasons { get; set; } = true; - /// - /// Gets or sets the subnets that are deemed to make up the LAN. - /// - public string[] LocalNetworkSubnets { get; set; } = Array.Empty(); - - /// - /// Gets or sets the interface addresses which Jellyfin will bind to. If empty, all interfaces will be used. - /// - public string[] LocalNetworkAddresses { get; set; } = Array.Empty(); - public string[] CodecsUsed { get; set; } = Array.Empty(); public List PluginRepositories { get; set; } = new List(); public bool EnableExternalContentInSuggestions { get; set; } = true; - /// - /// Gets or sets a value indicating whether the server should force connections over HTTPS. - /// - public bool RequireHttps { get; set; } = false; - - /// - /// Gets or sets the filter for remote IP connectivity. Used in conjuntion with . - /// - public string[] RemoteIPFilter { get; set; } = Array.Empty(); - - /// - /// Gets or sets a value indicating whether contains a blacklist or a whitelist. Default is a whitelist. - /// - public bool IsRemoteIPFilterBlacklist { get; set; } = false; - public int ImageExtractionTimeoutMs { get; set; } = 0; public PathSubstitution[] PathSubstitutions { get; set; } = Array.Empty(); - public string[] UninstalledPlugins { get; set; } = Array.Empty(); - /// /// Gets or sets a value indicating whether slow server responses should be logged as a warning. /// @@ -433,11 +216,6 @@ namespace MediaBrowser.Model.Configuration ///
public string[] CorsHosts { get; set; } = new[] { "*" }; - /// - /// Gets or sets the known proxies. - /// - public string[] KnownProxies { get; set; } = Array.Empty(); - /// /// Gets or sets the number of days we should retain activity logs. /// From 0485ff189948e4c0b6532e835b9f6740e89d0d40 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 24 Nov 2021 13:42:14 +0100 Subject: [PATCH 015/167] Create a store key constant for network --- Emby.Dlna/Main/DlnaEntryPoint.cs | 2 +- Jellyfin.Api/Controllers/StartupController.cs | 2 +- .../NetworkConfigurationFactory.cs | 6 +---- .../NetworkConfigurationStore.cs | 24 +++++++++++++++++++ Jellyfin.Networking/Manager/NetworkManager.cs | 2 +- 5 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index f35d90f21c..08f639d932 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -124,7 +124,7 @@ namespace Emby.Dlna.Main config); Current = this; - var netConfig = config.GetConfiguration("network"); + var netConfig = config.GetConfiguration(NetworkConfigurationStore.StoreKey); _disabled = appHost.ListenWithHttps && netConfig.RequireHttps; if (_disabled && _config.GetDlnaConfiguration().EnableServer) diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index a01a617fc0..c49bde93f1 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -93,7 +93,7 @@ namespace Jellyfin.Api.Controllers NetworkConfiguration settings = _config.GetNetworkConfiguration(); settings.EnableRemoteAccess = startupRemoteAccessDto.EnableRemoteAccess; settings.EnableUPnP = startupRemoteAccessDto.EnableAutomaticPortMapping; - _config.SaveConfiguration("network", settings); + _config.SaveConfiguration(NetworkConfigurationStore.StoreKey, settings); return NoContent(); } diff --git a/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs b/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs index ac0485d871..14726565aa 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfigurationFactory.cs @@ -16,11 +16,7 @@ namespace Jellyfin.Networking.Configuration { return new[] { - new ConfigurationStore - { - Key = "network", - ConfigurationType = typeof(NetworkConfiguration) - } + new NetworkConfigurationStore() }; } } diff --git a/Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs b/Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs new file mode 100644 index 0000000000..a268ebb68f --- /dev/null +++ b/Jellyfin.Networking/Configuration/NetworkConfigurationStore.cs @@ -0,0 +1,24 @@ +using MediaBrowser.Common.Configuration; + +namespace Jellyfin.Networking.Configuration +{ + /// + /// A configuration that stores network related settings. + /// + public class NetworkConfigurationStore : ConfigurationStore + { + /// + /// The name of the configuration in the storage. + /// + public const string StoreKey = "network"; + + /// + /// Initializes a new instance of the class. + /// + public NetworkConfigurationStore() + { + ConfigurationType = typeof(NetworkConfiguration); + Key = StoreKey; + } + } +} diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index cf002dc738..58b30ad2d2 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -727,7 +727,7 @@ namespace Jellyfin.Networking.Manager private void ConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs evt) { - if (evt.Key.Equals("network", StringComparison.Ordinal)) + if (evt.Key.Equals(NetworkConfigurationStore.StoreKey, StringComparison.Ordinal)) { UpdateSettings((NetworkConfiguration)evt.NewConfiguration); } From 0b871505a6d8caa124d3578ce915f6b4162a8ef2 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 24 Nov 2021 14:36:01 +0100 Subject: [PATCH 016/167] Remove stray datamember --- .../Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs index 98c845dc3f..a951f751e3 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Runtime.Serialization; using System.Xml; using System.Xml.Serialization; using Emby.Server.Implementations; @@ -111,7 +110,6 @@ public class CreateNetworkConfiguration : IMigrationRoutine public bool EnableIPV6 { get; set; } - [DataMember(Name = "EnableIPV4")] public bool EnableIPV4 { get; set; } = true; public bool IgnoreVirtualInterfaces { get; set; } = true; From 4890454935f6c56ff4d2fab0b71646311319c0ac Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 27 Nov 2021 07:28:58 -0700 Subject: [PATCH 017/167] Add additional provider parsing to series file name --- .../Library/Resolvers/TV/SeriesResolver.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 4e15acd182..64272fde1a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -187,11 +187,40 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV { var justName = Path.GetFileName(path); - var id = justName.GetAttributeValue("tvdbid"); - - if (!string.IsNullOrEmpty(id)) + var tvdbId = justName.GetAttributeValue("tvdbid"); + if (!string.IsNullOrEmpty(tvdbId)) { - item.SetProviderId(MetadataProvider.Tvdb, id); + item.SetProviderId(MetadataProvider.Tvdb, tvdbId); + } + + var tvmazeId = justName.GetAttributeValue("tvmazeid"); + if (!string.IsNullOrEmpty(tvmazeId)) + { + item.SetProviderId(MetadataProvider.TvMaze, tvmazeId); + } + + var tmdbId = justName.GetAttributeValue("tmdbid"); + if (!string.IsNullOrEmpty(tmdbId)) + { + item.SetProviderId(MetadataProvider.Tmdb, tmdbId); + } + + var anidbId = justName.GetAttributeValue("anidbid"); + if (!string.IsNullOrEmpty(anidbId)) + { + item.SetProviderId("AniDB", anidbId); + } + + var aniListId = justName.GetAttributeValue("anilistid"); + if (!string.IsNullOrEmpty(aniListId)) + { + item.SetProviderId("AniList", aniListId); + } + + var aniSearchId = justName.GetAttributeValue("anisearchid"); + if (!string.IsNullOrEmpty(aniSearchId)) + { + item.SetProviderId("AniSearch", aniSearchId); } } } From a87b87825dac31f3739a8dc0254548c3e81a241e Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 27 Nov 2021 16:01:27 +0100 Subject: [PATCH 018/167] Update Jellyfin.Server/Migrations/MigrationRunner.cs Co-authored-by: Cody Robibero --- Jellyfin.Server/Migrations/MigrationRunner.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 11b6c4912b..a6886c64a6 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -42,7 +42,6 @@ namespace Jellyfin.Server.Migrations typeof(Routines.MigrateAuthenticationDb) }; - /// /// Run all needed migrations. /// From 4a20ae6cb414e5ec06ea17dca2a1b44db4d187af Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sat, 27 Nov 2021 20:13:21 +0100 Subject: [PATCH 019/167] Allow default/forced tag without setting language --- .../MediaInfo/SubtitleResolver.cs | 21 ++++++++++--- .../MediaInfo/SubtitleResolverTests.cs | 31 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index dd4a5f061c..d0512ce575 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -120,6 +120,12 @@ namespace MediaBrowser.Providers.MediaInfo while (languageSpan.Length > 0) { var lastDot = languageSpan.LastIndexOf('.'); + if (lastDot < videoFileNameWithoutExtension.Length) + { + languageSpan = ReadOnlySpan.Empty; + break; + } + var currentSlice = languageSpan[lastDot..]; if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase) || currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase) @@ -133,12 +139,19 @@ namespace MediaBrowser.Providers.MediaInfo break; } - // Try to translate to three character code - // Be flexible and check against both the full and three character versions var language = languageSpan.ToString(); - var culture = _localization.FindLanguageInfo(language); + if (string.IsNullOrWhiteSpace(language)) + { + language = null; + } + else + { + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var culture = _localization.FindLanguageInfo(language); - language = culture == null ? language : culture.ThreeLetterISOLanguageName; + language = culture == null ? language : culture.ThreeLetterISOLanguageName; + } mediaStream = new MediaStream { diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs index c289a71129..33da277e35 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs @@ -80,6 +80,37 @@ namespace Jellyfin.Providers.Tests.MediaInfo } } + [Theory] + [InlineData("/video/My Video.mkv", "/video/My Video.srt", "srt", null, false, false)] + [InlineData("/video/My.Video.mkv", "/video/My.Video.srt", "srt", null, false, false)] + [InlineData("/video/My.Video.mkv", "/video/My.Video.foreign.srt", "srt", null, true, false)] + [InlineData("/video/My Video.mkv", "/video/My Video.forced.srt", "srt", null, true, false)] + [InlineData("/video/My.Video.mkv", "/video/My.Video.default.srt", "srt", null, false, true)] + [InlineData("/video/My.Video.mkv", "/video/My.Video.forced.default.srt", "srt", null, true, true)] + [InlineData("/video/My.Video.mkv", "/video/My.Video.en.srt", "srt", "en", false, false)] + [InlineData("/video/My.Video.mkv", "/video/My.Video.default.en.srt", "srt", "en", false, true)] + [InlineData("/video/My.Video.mkv", "/video/My.Video.default.forced.en.srt", "srt", "en", true, true)] + [InlineData("/video/My.Video.mkv", "/video/My.Video.en.default.forced.srt", "srt", "en", true, true)] + public void AddExternalSubtitleStreams_GivenSingleFile_ReturnsExpectedSubtitle(string videoPath, string file, string codec, string? language, bool isForced, bool isDefault) + { + var streams = new List(); + var expected = CreateMediaStream(file, codec, language, 0, isForced, isDefault); + + new SubtitleResolver(Mock.Of()).AddExternalSubtitleStreams(streams, videoPath, 0, new[] { file }); + + Assert.Single(streams); + + var actual = streams[0]; + + 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 () From 1df5b5034b944360a82957acd22a3b1dae040113 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sat, 27 Nov 2021 20:35:18 +0100 Subject: [PATCH 020/167] Address suppressed warnings --- .../MediaInfo/SubtitleResolver.cs | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index d0512ce575..ba284187ed 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CA1002, CS1591 - using System; using System.Collections.Generic; using System.IO; @@ -12,15 +8,30 @@ using MediaBrowser.Model.Globalization; namespace MediaBrowser.Providers.MediaInfo { + /// + /// Resolves external subtitles for videos. + /// public class SubtitleResolver { private readonly ILocalizationManager _localization; + /// + /// Initializes a new instance of the class. + /// + /// The localization manager. public SubtitleResolver(ILocalizationManager localization) { _localization = localization; } + /// + /// Retrieves the external subtitle streams for the provided video. + /// + /// The video to search from. + /// The stream index to start adding subtitle streams at. + /// The directory service to search for files. + /// True if the directory service cache should be cleared before searching. + /// The external subtitle streams located. public List GetExternalSubtitleStreams( Video video, int startIndex, @@ -56,6 +67,13 @@ namespace MediaBrowser.Providers.MediaInfo return streams; } + /// + /// Locates the external subtitle files for the provided video. + /// + /// The video to search from. + /// The directory service to search for files. + /// True if the directory service cache should be cleared before searching. + /// The external subtitle file paths located. public IEnumerable GetExternalSubtitleFiles( Video video, IDirectoryService directoryService, @@ -74,6 +92,13 @@ namespace MediaBrowser.Providers.MediaInfo } } + /// + /// Extracts the subtitle files from the provided list and adds them to the list of streams. + /// + /// The list of streams to add external subtitles to. + /// The path to the video file. + /// The stream index to start adding subtitle streams at. + /// The files to add if they are subtitles. public void AddExternalSubtitleStreams( List streams, string videoPath, From 065d3fa837bf5d66d4b4e5382257b4ce76b9d0b3 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 27 Nov 2021 16:10:43 -0700 Subject: [PATCH 021/167] performance++ --- .../Library/PathExtensions.cs | 23 ++++++++++++------- .../Resolvers/Movies/BoxSetResolver.cs | 2 +- .../Library/Resolvers/Movies/MovieResolver.cs | 6 ++--- .../Library/Resolvers/TV/SeriesResolver.cs | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index d5b855cdf2..8ce054c38c 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library /// The attrib. /// System.String. /// or is empty. - public static string? GetAttributeValue(this string str, string attribute) + public static string? GetAttributeValue(this ReadOnlySpan str, ReadOnlySpan attribute) { if (str.Length == 0) { @@ -28,17 +28,24 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("String can't be empty.", nameof(attribute)); } - string srch = "[" + attribute + "="; - int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - if (start != -1) + var openBracketIndex = str.IndexOf('['); + var equalsIndex = str.IndexOf('='); + var closingBracketIndex = str.IndexOf(']'); + while (openBracketIndex < equalsIndex && equalsIndex < closingBracketIndex) { - start += srch.Length; - int end = str.IndexOf(']', start); - return str.Substring(start, end - start); + if (str[(openBracketIndex + 1)..equalsIndex].Equals(attribute, StringComparison.OrdinalIgnoreCase)) + { + return str[(equalsIndex + 1)..closingBracketIndex].Trim().ToString(); + } + + str = str[(closingBracketIndex+ 1)..]; + openBracketIndex = str.IndexOf('['); + equalsIndex = str.IndexOf('='); + closingBracketIndex = str.IndexOf(']'); } // for imdbid we also accept pattern matching - if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase)) + if (attribute.Equals("imdbid", StringComparison.OrdinalIgnoreCase)) { var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId); return match ? imdbId.ToString() : null; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index e7abe1e6da..6cc04ea810 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private static void SetProviderIdFromPath(BaseItem item) { // we need to only look at the name of this actual item (not parents) - var justName = Path.GetFileName(item.Path); + var justName = Path.GetFileName(item.Path.AsSpan()); var id = justName.GetAttributeValue("tmdbid"); diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 732be0fe5c..ddd3fae882 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -342,9 +342,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (item is Movie || item is MusicVideo) { // We need to only look at the name of this actual item (not parents) - var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath); + var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path.AsSpan()) : Path.GetFileName(item.ContainingFolderPath.AsSpan()); - if (!string.IsNullOrEmpty(justName)) + if (!justName.IsEmpty) { // check for tmdb id var tmdbid = justName.GetAttributeValue("tmdbid"); @@ -358,7 +358,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (!string.IsNullOrEmpty(item.Path)) { // check for imdb id - we use full media path, as we can assume, that this will match in any use case (wither id in parent dir or in file name) - var imdbid = item.Path.GetAttributeValue("imdbid"); + var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid"); if (!string.IsNullOrWhiteSpace(imdbid)) { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 64272fde1a..46e36847dc 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// The path. private static void SetProviderIdFromPath(Series item, string path) { - var justName = Path.GetFileName(path); + var justName = Path.GetFileName(path.AsSpan()); var tvdbId = justName.GetAttributeValue("tvdbid"); if (!string.IsNullOrEmpty(tvdbId)) From 1df56335eec0430bef0d4dface75f69691134c10 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 27 Nov 2021 16:25:21 -0700 Subject: [PATCH 022/167] Add more tests --- .../Library/PathExtensionsTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index c5cc056f5d..d6cbe96474 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -11,6 +11,9 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("Superman: Red Son - tt10985510", "imdbid", "tt10985510")] [InlineData("Superman: Red Son", "imdbid", null)] [InlineData("Superman: Red Son", "something", null)] + [InlineData("Superman: Red Son [imdbid1=tt11111111][imdbid=tt10985510]", "imdbid", "tt10985510")] + [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "imdbid", "tt10985510")] + [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "tmdbid", "618355")] public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult) { Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute)); From beef6f0855956f75784be7d62de98fbd9d958f80 Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 28 Nov 2021 19:56:31 +0100 Subject: [PATCH 023/167] Don't query series twice --- MediaBrowser.Providers/TV/SeriesMetadataService.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index 9d223b4b66..770dc3e003 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -130,11 +130,12 @@ namespace MediaBrowser.Providers.TV /// The async task. private async Task FillInMissingSeasonsAsync(Series series, CancellationToken cancellationToken) { - var episodesInSeriesFolder = series.GetRecursiveChildren(i => i is Episode) - .Cast() + var seriesChildren = series.GetRecursiveChildren(i => i is Episode || i is Season); + var episodesInSeriesFolder = seriesChildren + .OfType() .Where(i => !i.IsInSeasonFolder); - List seasons = series.Children.OfType().ToList(); + List seasons = seriesChildren.OfType().ToList(); // Loop through the unique season numbers foreach (var episode in episodesInSeriesFolder) From 2ff786fd3df7b193f23efaa6d87d8d89f1b18c48 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Fri, 26 Nov 2021 11:39:52 +0000 Subject: [PATCH 024/167] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index b2dcf270c1..4dcb992930 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -39,7 +39,7 @@ "MixedContent": "Kandungan campuran", "Movies": "Filem", "Music": "Muzik", - "MusicVideos": "Muzik video", + "MusicVideos": "", "NameInstallFailed": "{0} pemasangan gagal", "NameSeasonNumber": "Musim {0}", "NameSeasonUnknown": "Musim Tidak Diketahui", From 3e1bbdd3d45a7a80b746a6c20f24a4336d05a748 Mon Sep 17 00:00:00 2001 From: Weevild Date: Thu, 25 Nov 2021 19:11:52 +0000 Subject: [PATCH 025/167] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- Emby.Server.Implementations/Localization/Core/sv.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index 6c772c6a24..1cef30b6c5 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -15,7 +15,7 @@ "Favorites": "Favoriter", "Folders": "Mappar", "Genres": "Genrer", - "HeaderAlbumArtists": "Artistens album", + "HeaderAlbumArtists": "Albumsartister", "HeaderContinueWatching": "Fortsätt kolla", "HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteArtists": "Favoritartister", From dc27e7618f9dab2e7bd891eed64e51e3c929c5d3 Mon Sep 17 00:00:00 2001 From: wolong gl Date: Sun, 28 Nov 2021 05:24:58 +0000 Subject: [PATCH 026/167] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index f9df627245..ac4eb644b7 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -11,7 +11,7 @@ "Collections": "合集", "DeviceOfflineWithName": "{0} 已断开", "DeviceOnlineWithName": "{0} 已连接", - "FailedLoginAttemptWithUserName": "来自 {0} 的失败登入", + "FailedLoginAttemptWithUserName": "从 {0} 尝试登录失败", "Favorites": "我的最爱", "Folders": "文件夹", "Genres": "风格", From 5addb24e9bf00d289189ca106df0880028c474f3 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 27 Nov 2021 10:13:15 +0000 Subject: [PATCH 027/167] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/ --- Emby.Server.Implementations/Localization/Core/pt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index 3c9ac2c241..a9dbd53ea2 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -11,7 +11,7 @@ "HeaderFavoriteEpisodes": "Episódios Favoritos", "HeaderFavoriteShows": "Séries Favoritas", "HeaderContinueWatching": "Continuar assistindo", - "HeaderAlbumArtists": "Discos do Artista", + "HeaderAlbumArtists": "Artistas do Álbum", "Genres": "Gêneros", "Folders": "Diretórios", "Favorites": "Favoritos", From 02ce95edafa1cf1997499374a3e91bc3cd4b8ad0 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 27 Nov 2021 09:47:28 +0000 Subject: [PATCH 028/167] Translated using Weblate (Serbian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sr/ --- Emby.Server.Implementations/Localization/Core/sr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json index 15fb34186b..2d6f3d53da 100644 --- a/Emby.Server.Implementations/Localization/Core/sr.json +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -50,7 +50,7 @@ "NameSeasonUnknown": "Непозната сезона", "NameSeasonNumber": "Сезона {0}", "NameInstallFailed": "Инсталација {0} није успела", - "MusicVideos": "Музички спотови", + "MusicVideos": "Музички видео", "Music": "Музика", "Movies": "Филмови", "MixedContent": "Мешовит садржај", From 502a5fc9324d0bc250278285049c1895a51082dd Mon Sep 17 00:00:00 2001 From: WontTell Date: Fri, 26 Nov 2021 09:20:41 +0000 Subject: [PATCH 029/167] Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/ --- .../Localization/Core/es_419.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json index 010531c9bc..2ca736ad92 100644 --- a/Emby.Server.Implementations/Localization/Core/es_419.json +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -15,7 +15,7 @@ "HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteShows": "Programas favoritos", "HeaderContinueWatching": "Continuar viendo", - "HeaderAlbumArtists": "Artistas del álbum", + "HeaderAlbumArtists": "Artistas de álbum", "Genres": "Géneros", "Folders": "Carpetas", "Favorites": "Favoritos", @@ -29,7 +29,7 @@ "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", "TaskRefreshChannels": "Actualizar canales", "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.", - "TaskCleanTranscode": "Limpiar directorio de transcodificado", + "TaskCleanTranscode": "Limpiar el directorio de transcodificaciones", "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", "TaskUpdatePlugins": "Actualizar complementos", "TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.", @@ -91,7 +91,7 @@ "NameSeasonUnknown": "Temporada desconocida", "NameSeasonNumber": "Temporada {0}", "NameInstallFailed": "Falló la instalación de {0}", - "MusicVideos": "Videos Musicales", + "MusicVideos": "Videos musicales", "Music": "Música", "MixedContent": "Contenido mezclado", "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", @@ -105,7 +105,7 @@ "Inherit": "Heredar", "HomeVideos": "Videos caseros", "HeaderRecordingGroups": "Grupos de grabación", - "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", + "FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}", "DeviceOnlineWithName": "{0} está conectado", "DeviceOfflineWithName": "{0} se ha desconectado", "ChapterNameValue": "Capítulo {0}", @@ -114,10 +114,10 @@ "Application": "Aplicación", "AppDeviceValues": "App: {0}, Dispositivo: {1}", "TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.", - "TaskCleanActivityLog": "Limpiar Registro de Actividades", + "TaskCleanActivityLog": "Limpiar registro de actividades", "Undefined": "Sin definir", "Forced": "Forzado", "Default": "Por defecto", - "TaskOptimizeDatabaseDescription": "Compacta la base de datos y restaura el espacio libre. Ejecutar esta tarea después de actualizar las librerías o realizar otros cambios que impliquen modificar las bases de datos puede mejorar la performance.", - "TaskOptimizeDatabase": "Optimización de base de datos" + "TaskOptimizeDatabaseDescription": "Compacta la base de datos y libera espacio. Ejecutar esta tarea después de escanear la biblioteca o hacer otros cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.", + "TaskOptimizeDatabase": "Optimizar base de datos" } From 6ef9ac6e533b13072e1f47a0c018b9ea91a2c7a0 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 27 Nov 2021 10:05:51 +0000 Subject: [PATCH 030/167] Translated using Weblate (Tamil) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ta/ --- Emby.Server.Implementations/Localization/Core/ta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index d6e9aa8e5a..e3a993625d 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -85,7 +85,7 @@ "HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்", "HeaderFavoriteAlbums": "பிடித்த ஆல்பங்கள்", "HeaderContinueWatching": "தொடர்ந்து பார்", - "HeaderAlbumArtists": "இசைக் கலைஞர்கள்", + "HeaderAlbumArtists": "கலைஞரின் ஆல்பம்", "Genres": "வகைகள்", "Favorites": "பிடித்தவை", "ChapterNameValue": "அத்தியாயம் {0}", From 19c9319d2ccf540841acb1f0549473547eb15bbb Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sun, 28 Nov 2021 11:45:50 +0000 Subject: [PATCH 031/167] Translated using Weblate (Albanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sq/ --- Emby.Server.Implementations/Localization/Core/sq.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sq.json b/Emby.Server.Implementations/Localization/Core/sq.json index 066ef55878..2766dab06a 100644 --- a/Emby.Server.Implementations/Localization/Core/sq.json +++ b/Emby.Server.Implementations/Localization/Core/sq.json @@ -74,7 +74,7 @@ "NameSeasonUnknown": "Sezon i panjohur", "NameSeasonNumber": "Sezoni {0}", "NameInstallFailed": "Instalimi i {0} dështoi", - "MusicVideos": "Video muzikore", + "MusicVideos": "Video Muzikore", "Music": "Muzikë", "Movies": "Filmat", "MixedContent": "Përmbajtje e përzier", @@ -96,7 +96,7 @@ "HeaderFavoriteArtists": "Artistët e preferuar", "HeaderFavoriteAlbums": "Albumet e preferuar", "HeaderContinueWatching": "Vazhdo të shikosh", - "HeaderAlbumArtists": "Artistët e Albumeve", + "HeaderAlbumArtists": "Artistët e albumeve", "Genres": "Zhanret", "Folders": "Skedarët", "Favorites": "Të preferuarat", From 2496c5d589773a216feda8e40a1cb316937b3d2e Mon Sep 17 00:00:00 2001 From: WWWesten Date: Fri, 26 Nov 2021 14:14:18 +0000 Subject: [PATCH 032/167] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index a5ff3031d7..cedf468f72 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -3,7 +3,7 @@ "Favorites": "Yêu Thích", "Folders": "Thư Mục", "Genres": "Thể Loại", - "HeaderAlbumArtists": "Album Nghệ sĩ", + "HeaderAlbumArtists": "Album nghệ sĩ", "HeaderContinueWatching": "Xem Tiếp", "HeaderLiveTV": "TV Trực Tiếp", "Movies": "Phim", From bcd84dedda5004468fa0a425fac65a3bab350e64 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 27 Nov 2021 10:12:04 +0000 Subject: [PATCH 033/167] Translated using Weblate (Pirate) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pr/ --- Emby.Server.Implementations/Localization/Core/pr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json index e3a3bfaf1c..81aa996d9e 100644 --- a/Emby.Server.Implementations/Localization/Core/pr.json +++ b/Emby.Server.Implementations/Localization/Core/pr.json @@ -1,5 +1,7 @@ { "Books": "Libros", "AuthenticationSucceededWithUserName": "{0} autentificado correctamente", - "Artists": "Artistas" + "Artists": "Artistas", + "Songs": "Shantees", + "Albums": "Ships" } From 09f2456919c2210b37e444d61876fe4f0683e2a2 Mon Sep 17 00:00:00 2001 From: rimasx Date: Sun, 28 Nov 2021 10:56:03 +0000 Subject: [PATCH 034/167] Translated using Weblate (Estonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/ --- Emby.Server.Implementations/Localization/Core/et.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json index 71f4a97e54..8db6a0b388 100644 --- a/Emby.Server.Implementations/Localization/Core/et.json +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -102,7 +102,7 @@ "Forced": "Sunnitud", "Folders": "Kaustad", "Favorites": "Lemmikud", - "FailedLoginAttemptWithUserName": "Ebaõnnestunud sisselogimiskatse kasutajalt {0}", + "FailedLoginAttemptWithUserName": "{0} - sisselogimine nurjus", "DeviceOnlineWithName": "{0} on ühendatud", "DeviceOfflineWithName": "{0} katkestas ühenduse", "Default": "Vaikimisi", From 20b0499b00d7426734e36f8ccc542a58a7583584 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 27 Nov 2021 10:01:19 +0000 Subject: [PATCH 035/167] Translated using Weblate (Telugu) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/te/ --- .../Localization/Core/te.json | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/te.json b/Emby.Server.Implementations/Localization/Core/te.json index 0967ef424b..a9a8ceae0e 100644 --- a/Emby.Server.Implementations/Localization/Core/te.json +++ b/Emby.Server.Implementations/Localization/Core/te.json @@ -1 +1,23 @@ -{} +{ + "ValueSpecialEpisodeName": "ప్రత్యేక - {0}", + "Sync": "సమకాలీకరించు", + "Songs": "పాటలు", + "Shows": "ప్రదర్శనలు", + "Playlists": "ప్లేజాబితాలు", + "Photos": "ఫోటోలు", + "MusicVideos": "మ్యూజిక్ వీడియోలు", + "Music": "సంగీతం", + "Movies": "సినిమాలు", + "HeaderContinueWatching": "చూడటం కొనసాగించండి", + "HeaderAlbumArtists": "ఆల్బమ్ కళాకారులు", + "Genres": "శైలులు", + "Forced": "బలవంతంగా", + "Folders": "ఫోల్డర్లు", + "Favorites": "ఇష్టమైనవి", + "Default": "డిఫాల్ట్", + "Collections": "సేకరణలు", + "Channels": "ఛానెల్‌లు", + "Books": "పుస్తకాలు", + "Artists": "కళాకారులు", + "Albums": "ఆల్బమ్‌లు" +} From c677b4f6b7f7e874097aa2cee866d9ed1e574178 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 27 Nov 2021 10:43:38 +0000 Subject: [PATCH 036/167] Translated using Weblate (Mongolian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mn/ --- Emby.Server.Implementations/Localization/Core/mn.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/mn.json b/Emby.Server.Implementations/Localization/Core/mn.json index ab7d423b58..7421d42fb4 100644 --- a/Emby.Server.Implementations/Localization/Core/mn.json +++ b/Emby.Server.Implementations/Localization/Core/mn.json @@ -9,5 +9,6 @@ "Genres": "Төрөл зүйл", "Favorites": "Дуртай", "Collections": "Багц", - "Artists": "Жүжигчин" + "Artists": "Зураачуд", + "Albums": "Цомгууд" } From 148fcb1bb82d734136a5d19664276b47e71f2aa1 Mon Sep 17 00:00:00 2001 From: "Brian J. Murrell" Date: Tue, 30 Nov 2021 01:18:27 -0500 Subject: [PATCH 037/167] Put low port privilege into an optional subpackage Move "AmbientCapabilities=CAP_NET_BIND_SERVICE" to the "[Service]" section of the optional "lowport" unit drop-in file and package that drop-in in a separate, optionally installable jellyfin-server-lowports subpackage. This isolates the CAP_NET_BIND_SERVICE capability to only installations that desire it. Signed-off-by: Brian J. Murrell --- fedora/jellyfin-server-lowports.conf | 4 ++++ fedora/jellyfin.service | 1 - fedora/jellyfin.spec | 20 +++++++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 fedora/jellyfin-server-lowports.conf diff --git a/fedora/jellyfin-server-lowports.conf b/fedora/jellyfin-server-lowports.conf new file mode 100644 index 0000000000..eeb48a4e4b --- /dev/null +++ b/fedora/jellyfin-server-lowports.conf @@ -0,0 +1,4 @@ +# This allows Jellyfin to bind to low ports such as 80 and/or 443 + +[Service] +AmbientCapabilities=CAP_NET_BIND_SERVICE \ No newline at end of file diff --git a/fedora/jellyfin.service b/fedora/jellyfin.service index b91e1a566e..f706b0ad3f 100644 --- a/fedora/jellyfin.service +++ b/fedora/jellyfin.service @@ -3,7 +3,6 @@ After=network-online.target Description=Jellyfin is a free software media system that puts you in control of managing and streaming your media. [Service] -AmbientCapabilities=CAP_NET_BIND_SERVICE EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin ExecStart=/usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 47dee7c13f..a4584364ef 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -12,7 +12,7 @@ Release: 1%{?dist} Summary: The Free Software Media System License: GPLv3 URL: https://jellyfin.org -# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz` +# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz` Source0: jellyfin-server-%{version}.tar.gz Source11: jellyfin.service Source12: jellyfin.env @@ -20,6 +20,7 @@ Source13: jellyfin.sudoers Source14: restart.sh Source15: jellyfin.override.conf Source16: jellyfin-firewalld.xml +Source17: jellyfin-server-lowports.conf %{?systemd_requires} BuildRequires: systemd @@ -45,6 +46,16 @@ Requires: libcurl, fontconfig, freetype, openssl, glibc, libicu, at, sudo %description server The Jellyfin media server backend. +%package server-lowports +# RPMfusion free +Summary: The Free Software Media System Server backend. Low-port binding. +Requires: jellyfin-server + +%description server-lowports +The Jellyfin media server backend low port binding package. This package +enables binding to ports < 1024. You would install this if you want +the Jellyfin server to bind to ports 80 and/or 443 for example. + %prep %autosetup -n jellyfin-server-%{version} -b 0 @@ -57,6 +68,7 @@ dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE %{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf +%{__install} -D -m 0644 %{SOURCE17} %{buildroot}%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf %{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json %{__mkdir} -p %{buildroot}%{_bindir} tee %{buildroot}%{_bindir}/jellyfin << EOF @@ -95,6 +107,9 @@ EOF %attr(750,jellyfin,jellyfin) %dir %{_var}/cache/jellyfin %{_datadir}/licenses/jellyfin/LICENSE +%files server-lowports +%{_unitdir}/jellyfin.service.d/jellyfin-server-lowports.conf + %pre server getent group jellyfin >/dev/null || groupadd -r jellyfin getent passwd jellyfin >/dev/null || \ @@ -137,6 +152,9 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Mon Nov 29 2021 Brian J. Murrell +- Add jellyfin-server-lowports.service drop-in in a server-lowports + subpackage to allow binding to low ports * Fri Dec 04 2020 Jellyfin Packaging Team - Forthcoming stable release * Mon Jul 27 2020 Jellyfin Packaging Team From e77846295565151be38fa33513ed6ca5c74e28c1 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 30 Nov 2021 14:31:32 +0100 Subject: [PATCH 038/167] Use SSL for tmdb images --- MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index cb644c8ca1..4d3bc61e58 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -498,7 +498,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return null; } - return _tmDbClient.GetImageUrl(size, path).ToString(); + return _tmDbClient.GetImageUrl(size, path, true).ToString(); } /// From eaa003775f641bc77a0216f145933a483ea0802b Mon Sep 17 00:00:00 2001 From: Marius Luca Date: Tue, 30 Nov 2021 16:09:02 +0200 Subject: [PATCH 039/167] - take into account the streams dlnaheaders query parameter set by the DidlBuilder NormalizeDlnaMediaUrl function --- Jellyfin.Api/Helpers/StreamingHelpers.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 1b8f24c27d..ed071bcd70 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -90,6 +90,7 @@ namespace Jellyfin.Api.Helpers } var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) || + streamingRequest.StreamOptions.ContainsKey("dlnaheaders") || string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase); var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper) From 997816443862a8db68f314a6d23ef076c9661c01 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Mon, 22 Nov 2021 21:47:52 +0100 Subject: [PATCH 040/167] Add support for external audio files --- MediaBrowser.Controller/Entities/Video.cs | 6 + .../MediaEncoding/EncodingHelper.cs | 21 +- .../MediaInfo/AudioResolver.cs | 275 ++++++++++++++++++ .../MediaInfo/FFProbeProvider.cs | 11 + .../MediaInfo/FFProbeVideoInfo.cs | 25 ++ 5 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 MediaBrowser.Providers/MediaInfo/AudioResolver.cs diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index de42c67d38..56e955adad 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -97,6 +97,12 @@ namespace MediaBrowser.Controller.Entities /// The subtitle paths. public string[] SubtitleFiles { get; set; } + /// + /// Gets or sets the audio paths. + /// + /// The audio paths. + public string[] AudioFiles { get; set; } + /// /// Gets or sets a value indicating whether this instance has subtitles. /// diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5715194b85..5326ecd20c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -678,6 +678,12 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append("-i ") .Append(GetInputPathArgument(state)); + if (state.AudioStream.IsExternal) + { + arg.Append(" -i ") + .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); + } + if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) @@ -1999,10 +2005,17 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null) { - args += string.Format( - CultureInfo.InvariantCulture, - " -map 0:{0}", - state.AudioStream.Index); + if (state.AudioStream.IsExternal) + { + args += " -map 1:a"; + } + else + { + args += string.Format( + CultureInfo.InvariantCulture, + " -map 0:{0}", + state.AudioStream.Index); + } } else { diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs new file mode 100644 index 0000000000..fce2fa5512 --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -0,0 +1,275 @@ +#nullable disable + +#pragma warning disable CA1002, CS1591 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.MediaInfo; + +namespace MediaBrowser.Providers.MediaInfo +{ + public class AudioResolver + { + private readonly ILocalizationManager _localization; + + private readonly IMediaEncoder _mediaEncoder; + + private readonly CancellationToken _cancellationToken; + + public AudioResolver(ILocalizationManager localization, IMediaEncoder mediaEncoder, CancellationToken cancellationToken = default) + { + _localization = localization; + _mediaEncoder = mediaEncoder; + _cancellationToken = cancellationToken; + } + + public List GetExternalAudioStreams( + Video video, + int startIndex, + IDirectoryService directoryService, + bool clearCache) + { + var streams = new List(); + + if (!video.IsFileProtocol) + { + return streams; + } + + AddExternalAudioStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); + + startIndex += streams.Count; + + string folder = video.GetInternalMetadataPath(); + + if (!Directory.Exists(folder)) + { + return streams; + } + + try + { + AddExternalAudioStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); + } + catch (IOException) + { + } + + return streams; + } + + public IEnumerable GetExternalAudioFiles( + Video video, + IDirectoryService directoryService, + bool clearCache) + { + if (!video.IsFileProtocol) + { + yield break; + } + + var streams = GetExternalAudioStreams(video, 0, directoryService, clearCache); + + foreach (var stream in streams) + { + yield return stream.Path; + } + } + + public void AddExternalAudioStreams( + List streams, + string videoPath, + int startIndex, + IReadOnlyList files) + { + var videoFileNameWithoutExtension = NormalizeFilenameForAudioComparison(videoPath); + + for (var i = 0; i < files.Count; i++) + { + + var fullName = files[i]; + var extension = Path.GetExtension(fullName.AsSpan()); + if (!IsAudioExtension(extension)) + { + continue; + } + + Model.MediaInfo.MediaInfo mediaInfo = GetMediaInfo(fullName).Result; + MediaStream mediaStream = mediaInfo.MediaStreams.First(); + mediaStream.Index = startIndex++; + mediaStream.Type = MediaStreamType.Audio; + mediaStream.IsExternal = true; + mediaStream.Path = fullName; + mediaStream.IsDefault = false; + mediaStream.Title = null; + + var fileNameWithoutExtension = NormalizeFilenameForAudioComparison(fullName); + + // The audio 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.Path = fullName; + } + else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + + // Support xbmc naming conventions - 300.spanish.m4a + var languageSpan = fileNameWithoutExtension; + while (languageSpan.Length > 0) + { + var lastDot = languageSpan.LastIndexOf('.'); + var currentSlice = languageSpan[lastDot..]; + languageSpan = languageSpan[(lastDot + 1)..]; + break; + } + + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var language = languageSpan.ToString(); + var culture = _localization.FindLanguageInfo(language); + + language = culture == null ? language : culture.ThreeLetterISOLanguageName; + mediaStream.Language = language; + } + else + { + continue; + } + + mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); + + streams.Add(mediaStream); + } + } + + private static bool IsAudioExtension(ReadOnlySpan extension) + { + String[] audioExtensions = new[] + { + ".nsv", + ".m4a", + ".flac", + ".aac", + ".strm", + ".pls", + ".rm", + ".mpa", + ".wav", + ".wma", + ".ogg", + ".opus", + ".mp3", + ".mp2", + ".mod", + ".amf", + ".669", + ".dmf", + ".dsm", + ".far", + ".gdm", + ".imf", + ".it", + ".m15", + ".med", + ".okt", + ".s3m", + ".stm", + ".sfx", + ".ult", + ".uni", + ".xm", + ".sid", + ".ac3", + ".dts", + ".cue", + ".aif", + ".aiff", + ".ape", + ".mac", + ".mpc", + ".mp+", + ".mpp", + ".shn", + ".wv", + ".nsf", + ".spc", + ".gym", + ".adplug", + ".adx", + ".dsp", + ".adp", + ".ymf", + ".ast", + ".afc", + ".hps", + ".xsp", + ".acc", + ".m4b", + ".oga", + ".dsf", + ".mka" + }; + + foreach (String audioExtension in audioExtensions) + { + if (extension.Equals(audioExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + private Task GetMediaInfo(string path) + { + _cancellationToken.ThrowIfCancellationRequested(); + + return _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaType = DlnaProfileType.Audio, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = MediaProtocol.File + } + }, + _cancellationToken); + } + + private static ReadOnlySpan NormalizeFilenameForAudioComparison(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 AddExternalAudioStreams( + List streams, + string folder, + string videoPath, + int startIndex, + IDirectoryService directoryService, + bool clearCache) + { + var files = directoryService.GetFilePaths(folder, clearCache, true); + + AddExternalAudioStreams(streams, videoPath, startIndex, files); + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index d4b5d8655c..1e7fcf2d20 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -50,6 +50,8 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; + private readonly AudioResolver _audioResolver; + private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); public FFProbeProvider( @@ -78,6 +80,7 @@ namespace MediaBrowser.Providers.MediaInfo _mediaSourceManager = mediaSourceManager; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + _audioResolver = new AudioResolver(BaseItem.LocalizationManager, mediaEncoder); } public string Name => "ffprobe"; @@ -111,6 +114,14 @@ namespace MediaBrowser.Providers.MediaInfo return true; } + if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder + && !video.AudioFiles.SequenceEqual( + _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) + { + _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path); + return true; + } + return false; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 4ab15f60e2..8db095416c 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -214,6 +214,8 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + AddExternalAudio(video, mediaStreams, options, cancellationToken); + var libraryOptions = _libraryManager.GetLibraryOptions(video); if (mediaInfo != null) @@ -574,6 +576,29 @@ namespace MediaBrowser.Providers.MediaInfo currentStreams.AddRange(externalSubtitleStreams); } + /// + /// Adds the external audio. + /// + /// The video. + /// The current streams. + /// The refreshOptions. + /// The cancellation token. + private void AddExternalAudio( + Video video, + List currentStreams, + MetadataRefreshOptions options, + CancellationToken cancellationToken) + { + var audioResolver = new AudioResolver(_localization, _mediaEncoder, cancellationToken); + + var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1); + var externalAudioStreams = audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false); + + video.AudioFiles = externalAudioStreams.Select(i => i.Path).ToArray(); + + currentStreams.AddRange(externalAudioStreams); + } + /// /// Creates dummy chapters. /// From c1a8385c9ceb70d9ad4301e7032e1719fe1bdc23 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Wed, 24 Nov 2021 21:53:36 +0100 Subject: [PATCH 041/167] Shorten calculation of audio startIndex in MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 8db095416c..2d49e43cad 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -591,7 +591,7 @@ namespace MediaBrowser.Providers.MediaInfo { var audioResolver = new AudioResolver(_localization, _mediaEncoder, cancellationToken); - var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1); + var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; var externalAudioStreams = audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false); video.AudioFiles = externalAudioStreams.Select(i => i.Path).ToArray(); From 5e91f50c437adcf28cc80eba970c87fddf4f0c9f Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sat, 27 Nov 2021 12:10:49 +0100 Subject: [PATCH 042/167] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5c4a031bc1..d52e133249 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -150,6 +150,7 @@ - [ianjazz246](https://github.com/ianjazz246) - [peterspenler](https://github.com/peterspenler) - [MBR-0001](https://github.com/MBR-0001) + - [jonas-resch](https://github.com/jonas-resch) # Emby Contributors From a68e58556c49ee9bc1c27fac696ffc9170c95e84 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sat, 27 Nov 2021 12:10:57 +0100 Subject: [PATCH 043/167] Implement code feedback - Rewrite AudioResolver - Use async & await instead of .Result - Add support for audio containers with multiple audio streams (e.g. mka) - Fix bug when using external subtitle and external audio streams at the same time --- .../MediaEncoding/EncodingHelper.cs | 21 +- .../MediaInfo/AudioResolver.cs | 279 ++++++------------ .../MediaInfo/FFProbeProvider.cs | 16 +- .../MediaInfo/FFProbeVideoInfo.cs | 16 +- 4 files changed, 118 insertions(+), 214 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5326ecd20c..5695ee2dbb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -678,12 +678,6 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append("-i ") .Append(GetInputPathArgument(state)); - if (state.AudioStream.IsExternal) - { - arg.Append(" -i ") - .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); - } - if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) @@ -702,6 +696,12 @@ namespace MediaBrowser.Controller.MediaEncoding arg.Append(" -i \"").Append(subtitlePath).Append('\"'); } + if (state.AudioStream != null && state.AudioStream.IsExternal) + { + arg.Append(" -i ") + .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); + } + return arg.ToString(); } @@ -2007,7 +2007,14 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.AudioStream.IsExternal) { - args += " -map 1:a"; + int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; + int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); + + args += string.Format( + CultureInfo.InvariantCulture, + " -map {0}:{1}", + externalAudioMapIndex, + externalAudioStream); } else { diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index fce2fa5512..8d5f8d86ec 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -1,13 +1,13 @@ -#nullable disable - #pragma warning disable CA1002, CS1591 using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; +using Emby.Naming.Audio; +using Emby.Naming.Common; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; @@ -21,24 +21,15 @@ namespace MediaBrowser.Providers.MediaInfo { public class AudioResolver { - private readonly ILocalizationManager _localization; - - private readonly IMediaEncoder _mediaEncoder; - - private readonly CancellationToken _cancellationToken; - - public AudioResolver(ILocalizationManager localization, IMediaEncoder mediaEncoder, CancellationToken cancellationToken = default) - { - _localization = localization; - _mediaEncoder = mediaEncoder; - _cancellationToken = cancellationToken; - } - - public List GetExternalAudioStreams( + public async Task> GetExternalAudioStreams( Video video, int startIndex, IDirectoryService directoryService, - bool clearCache) + NamingOptions namingOptions, + bool clearCache, + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + CancellationToken cancellationToken) { var streams = new List(); @@ -47,198 +38,117 @@ namespace MediaBrowser.Providers.MediaInfo return streams; } - AddExternalAudioStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); + List paths = GetExternalAudioFiles(video, directoryService, namingOptions, clearCache); - startIndex += streams.Count; - - string folder = video.GetInternalMetadataPath(); - - if (!Directory.Exists(folder)) - { - return streams; - } - - try - { - AddExternalAudioStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); - } - catch (IOException) - { - } + await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken); return streams; } - public IEnumerable GetExternalAudioFiles( + public List GetExternalAudioFiles( Video video, IDirectoryService directoryService, + NamingOptions namingOptions, bool clearCache) { + List paths = new List(); + if (!video.IsFileProtocol) { - yield break; + return paths; } - var streams = GetExternalAudioStreams(video, 0, directoryService, clearCache); + paths.AddRange(GetAudioFilesFromFolder(video.ContainingFolderPath, video.Path, directoryService, namingOptions, clearCache)); + paths.AddRange(GetAudioFilesFromFolder(video.GetInternalMetadataPath(), video.Path, directoryService, namingOptions, clearCache)); - foreach (var stream in streams) - { - yield return stream.Path; - } + return paths; } - public void AddExternalAudioStreams( - List streams, - string videoPath, - int startIndex, - IReadOnlyList files) + private List GetAudioFilesFromFolder( + string folder, + string videoFileName, + IDirectoryService directoryService, + NamingOptions namingOptions, + bool clearCache) { - var videoFileNameWithoutExtension = NormalizeFilenameForAudioComparison(videoPath); + List paths = new List(); + string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoFileName); - for (var i = 0; i < files.Count; i++) + if (!Directory.Exists(folder)) { + return paths; + } - var fullName = files[i]; - var extension = Path.GetExtension(fullName.AsSpan()); - if (!IsAudioExtension(extension)) + var files = directoryService.GetFilePaths(folder, clearCache, true); + for (int i = 0; i < files.Count; i++) + { + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(files[i]); + + if (!AudioFileParser.IsAudioFile(files[i], namingOptions)) { continue; } - Model.MediaInfo.MediaInfo mediaInfo = GetMediaInfo(fullName).Result; - MediaStream mediaStream = mediaInfo.MediaStreams.First(); - mediaStream.Index = startIndex++; - mediaStream.Type = MediaStreamType.Audio; - mediaStream.IsExternal = true; - mediaStream.Path = fullName; - mediaStream.IsDefault = false; - mediaStream.Title = null; - - var fileNameWithoutExtension = NormalizeFilenameForAudioComparison(fullName); - // The audio 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.Path = fullName; - } - else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) || + (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' - && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) { + paths.Add(files[i]); + } + } - // Support xbmc naming conventions - 300.spanish.m4a - var languageSpan = fileNameWithoutExtension; - while (languageSpan.Length > 0) + return paths; + } + + public async Task AddExternalAudioStreams( + List streams, + List paths, + int startIndex, + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + CancellationToken cancellationToken) + { + foreach (string path in paths) + { + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); + Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, mediaEncoder, cancellationToken); + + foreach (MediaStream mediaStream in mediaInfo.MediaStreams) + { + mediaStream.Index = startIndex++; + mediaStream.Type = MediaStreamType.Audio; + mediaStream.IsExternal = true; + mediaStream.Path = path; + mediaStream.IsDefault = false; + mediaStream.Title = null; + + if (mediaStream.Language == null) { - var lastDot = languageSpan.LastIndexOf('.'); - var currentSlice = languageSpan[lastDot..]; - languageSpan = languageSpan[(lastDot + 1)..]; - break; + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var language = StringExtensions.RightPart(fileNameWithoutExtension, '.').ToString(); + + if (language != fileNameWithoutExtension) + { + var culture = localizationManager.FindLanguageInfo(language); + + language = culture == null ? language : culture.ThreeLetterISOLanguageName; + mediaStream.Language = language; + } } - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var language = languageSpan.ToString(); - var culture = _localization.FindLanguageInfo(language); - - language = culture == null ? language : culture.ThreeLetterISOLanguageName; - mediaStream.Language = language; + streams.Add(mediaStream); } - else - { - continue; - } - - mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); - - streams.Add(mediaStream); } } - private static bool IsAudioExtension(ReadOnlySpan extension) + private Task GetMediaInfo(string path, IMediaEncoder mediaEncoder, CancellationToken cancellationToken) { - String[] audioExtensions = new[] - { - ".nsv", - ".m4a", - ".flac", - ".aac", - ".strm", - ".pls", - ".rm", - ".mpa", - ".wav", - ".wma", - ".ogg", - ".opus", - ".mp3", - ".mp2", - ".mod", - ".amf", - ".669", - ".dmf", - ".dsm", - ".far", - ".gdm", - ".imf", - ".it", - ".m15", - ".med", - ".okt", - ".s3m", - ".stm", - ".sfx", - ".ult", - ".uni", - ".xm", - ".sid", - ".ac3", - ".dts", - ".cue", - ".aif", - ".aiff", - ".ape", - ".mac", - ".mpc", - ".mp+", - ".mpp", - ".shn", - ".wv", - ".nsf", - ".spc", - ".gym", - ".adplug", - ".adx", - ".dsp", - ".adp", - ".ymf", - ".ast", - ".afc", - ".hps", - ".xsp", - ".acc", - ".m4b", - ".oga", - ".dsf", - ".mka" - }; + cancellationToken.ThrowIfCancellationRequested(); - foreach (String audioExtension in audioExtensions) - { - if (extension.Equals(audioExtension, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - - private Task GetMediaInfo(string path) - { - _cancellationToken.ThrowIfCancellationRequested(); - - return _mediaEncoder.GetMediaInfo( + return mediaEncoder.GetMediaInfo( new MediaInfoRequest { MediaType = DlnaProfileType.Audio, @@ -248,28 +158,7 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = MediaProtocol.File } }, - _cancellationToken); - } - - private static ReadOnlySpan NormalizeFilenameForAudioComparison(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 AddExternalAudioStreams( - List streams, - string folder, - string videoPath, - int startIndex, - IDirectoryService directoryService, - bool clearCache) - { - var files = directoryService.GetFilePaths(folder, clearCache, true); - - AddExternalAudioStreams(streams, videoPath, startIndex, files); + cancellationToken); } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 1e7fcf2d20..98909c94ec 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Emby.Naming.Common; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -50,10 +51,10 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; - private readonly AudioResolver _audioResolver; - private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); + private readonly NamingOptions _namingOptions; + public FFProbeProvider( ILogger logger, IMediaSourceManager mediaSourceManager, @@ -65,7 +66,8 @@ namespace MediaBrowser.Providers.MediaInfo IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + NamingOptions namingOptions) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -78,9 +80,9 @@ namespace MediaBrowser.Providers.MediaInfo _chapterManager = chapterManager; _libraryManager = libraryManager; _mediaSourceManager = mediaSourceManager; + _namingOptions = namingOptions; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); - _audioResolver = new AudioResolver(BaseItem.LocalizationManager, mediaEncoder); } public string Name => "ffprobe"; @@ -114,9 +116,10 @@ namespace MediaBrowser.Providers.MediaInfo return true; } + AudioResolver audioResolver = new AudioResolver(); if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder && !video.AudioFiles.SequenceEqual( - _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) + audioResolver.GetExternalAudioFiles(video, directoryService, _namingOptions, false), StringComparer.Ordinal)) { _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path); return true; @@ -199,7 +202,8 @@ namespace MediaBrowser.Providers.MediaInfo _config, _subtitleManager, _chapterManager, - _libraryManager); + _libraryManager, + _namingOptions); return prober.ProbeVideo(item, options, cancellationToken); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 2d49e43cad..39950db70f 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using DvdLib.Ifo; +using Emby.Naming.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -44,6 +45,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; + private readonly NamingOptions _namingOptions; private readonly IMediaSourceManager _mediaSourceManager; private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; @@ -59,7 +61,8 @@ namespace MediaBrowser.Providers.MediaInfo IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + NamingOptions namingOptions) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -71,6 +74,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; _chapterManager = chapterManager; _libraryManager = libraryManager; + _namingOptions = namingOptions; _mediaSourceManager = mediaSourceManager; } @@ -214,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - AddExternalAudio(video, mediaStreams, options, cancellationToken); + await AddExternalAudio(video, mediaStreams, options, cancellationToken); var libraryOptions = _libraryManager.GetLibraryOptions(video); @@ -583,18 +587,18 @@ namespace MediaBrowser.Providers.MediaInfo /// The current streams. /// The refreshOptions. /// The cancellation token. - private void AddExternalAudio( + private async Task AddExternalAudio( Video video, List currentStreams, MetadataRefreshOptions options, CancellationToken cancellationToken) { - var audioResolver = new AudioResolver(_localization, _mediaEncoder, cancellationToken); + var audioResolver = new AudioResolver(); var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; - var externalAudioStreams = audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false); + var externalAudioStreams = await audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, _namingOptions, false, _localization, _mediaEncoder, cancellationToken); - video.AudioFiles = externalAudioStreams.Select(i => i.Path).ToArray(); + video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray(); currentStreams.AddRange(externalAudioStreams); } From f1862f9b1a9d6daa0315308671cfb6d0fdd6a989 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:50:24 +0100 Subject: [PATCH 044/167] Add ConfigureAwait false to MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Cody Robibero --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 8d5f8d86ec..2a50dc5d11 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Providers.MediaInfo List paths = GetExternalAudioFiles(video, directoryService, namingOptions, clearCache); - await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken); + await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken).ConfigureAwait(false); return streams; } From a3c5afa443dca5cfd90ff1a33b7af9dbfe751ff4 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:50:39 +0100 Subject: [PATCH 045/167] Add ConfigureAwait false MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs Co-authored-by: Cody Robibero --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 39950db70f..7450205993 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -218,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - await AddExternalAudio(video, mediaStreams, options, cancellationToken); + await AddExternalAudio(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); var libraryOptions = _libraryManager.GetLibraryOptions(video); From 9433072f90593a43d2faa4eb645eb521a257205c Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:52:03 +0100 Subject: [PATCH 046/167] Only search in video folder for external audio files Don't search in video metadata folder since audio files won't be stored there Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 2a50dc5d11..2f73819834 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -59,7 +59,6 @@ namespace MediaBrowser.Providers.MediaInfo } paths.AddRange(GetAudioFilesFromFolder(video.ContainingFolderPath, video.Path, directoryService, namingOptions, clearCache)); - paths.AddRange(GetAudioFilesFromFolder(video.GetInternalMetadataPath(), video.Path, directoryService, namingOptions, clearCache)); return paths; } From 61b191d34556a0dad260344e83b9f40ac12f28ce Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:55:19 +0100 Subject: [PATCH 047/167] Fix indentation in MediaBrowser.Providers/MediaInfo/AudioResolver.cs If statement which checks if filename of audio and video file match or if audio file starts with video filename Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 2f73819834..481a82df49 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -89,10 +89,10 @@ namespace MediaBrowser.Providers.MediaInfo } // The audio 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) || - (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length - && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' - && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) + || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) { paths.Add(files[i]); } From d016d483ae5ab79f6f1171ce7f6ca99b8cd91cf0 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:56:17 +0100 Subject: [PATCH 048/167] Change return type from Task> to Task> in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 481a82df49..bd778684ed 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.MediaInfo { public class AudioResolver { - public async Task> GetExternalAudioStreams( + public async Task> GetExternalAudioStreams( Video video, int startIndex, IDirectoryService directoryService, From bbf1399826f92241a653adbb808a4cc7ea6a5542 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:57:53 +0100 Subject: [PATCH 049/167] Check language for null or empty instead of only null in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index bd778684ed..54d8525778 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Providers.MediaInfo mediaStream.IsDefault = false; mediaStream.Title = null; - if (mediaStream.Language == null) + if (string.IsNullOrEmpty(mediaStream.Language)) { // Try to translate to three character code // Be flexible and check against both the full and three character versions From 9d34d6339a2d677139573ada98b92318de298653 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Sat, 27 Nov 2021 16:58:37 +0100 Subject: [PATCH 050/167] Change return type from List to IEnumerable in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 54d8525778..ccac450f6c 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Providers.MediaInfo return streams; } - public List GetExternalAudioFiles( + public IEnumerable GetExternalAudioFiles( Video video, IDirectoryService directoryService, NamingOptions namingOptions, From 0894a6193f025a9cec5c735226a8487caa2bc66b Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sun, 28 Nov 2021 14:03:52 +0100 Subject: [PATCH 051/167] Implement coding standards from 2nd code feedback --- .../MediaEncoding/EncodingHelper.cs | 3 +- .../MediaInfo/AudioResolver.cs | 146 ++++++++---------- .../MediaInfo/FFProbeProvider.cs | 15 +- .../MediaInfo/FFProbeVideoInfo.cs | 21 +-- 4 files changed, 82 insertions(+), 103 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5695ee2dbb..5712303123 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -698,8 +698,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null && state.AudioStream.IsExternal) { - arg.Append(" -i ") - .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path)); + arg.Append(" -i \"").Append(state.AudioStream.Path).Append("\""); } return arg.ToString(); diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index ccac450f6c..d23afdc3b9 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -21,98 +21,37 @@ namespace MediaBrowser.Providers.MediaInfo { public class AudioResolver { - public async Task> GetExternalAudioStreams( + private readonly ILocalizationManager _localizationManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly NamingOptions _namingOptions; + + public AudioResolver( + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + NamingOptions namingOptions) + { + _localizationManager = localizationManager; + _mediaEncoder = mediaEncoder; + _namingOptions = namingOptions; + } + + public async IAsyncEnumerable GetExternalAudioStreams( Video video, int startIndex, IDirectoryService directoryService, - NamingOptions namingOptions, bool clearCache, - ILocalizationManager localizationManager, - IMediaEncoder mediaEncoder, CancellationToken cancellationToken) { - var streams = new List(); - if (!video.IsFileProtocol) { - return streams; + yield break; } - List paths = GetExternalAudioFiles(video, directoryService, namingOptions, clearCache); - - await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken).ConfigureAwait(false); - - return streams; - } - - public IEnumerable GetExternalAudioFiles( - Video video, - IDirectoryService directoryService, - NamingOptions namingOptions, - bool clearCache) - { - List paths = new List(); - - if (!video.IsFileProtocol) - { - return paths; - } - - paths.AddRange(GetAudioFilesFromFolder(video.ContainingFolderPath, video.Path, directoryService, namingOptions, clearCache)); - - return paths; - } - - private List GetAudioFilesFromFolder( - string folder, - string videoFileName, - IDirectoryService directoryService, - NamingOptions namingOptions, - bool clearCache) - { - List paths = new List(); - string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoFileName); - - if (!Directory.Exists(folder)) - { - return paths; - } - - var files = directoryService.GetFilePaths(folder, clearCache, true); - for (int i = 0; i < files.Count; i++) - { - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(files[i]); - - if (!AudioFileParser.IsAudioFile(files[i], namingOptions)) - { - continue; - } - - // The audio 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) - || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length - && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' - && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) - { - paths.Add(files[i]); - } - } - - return paths; - } - - public async Task AddExternalAudioStreams( - List streams, - List paths, - int startIndex, - ILocalizationManager localizationManager, - IMediaEncoder mediaEncoder, - CancellationToken cancellationToken) - { + IEnumerable paths = GetExternalAudioFiles(video, directoryService, clearCache); foreach (string path in paths) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); - Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, mediaEncoder, cancellationToken); + Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken); foreach (MediaStream mediaStream in mediaInfo.MediaStreams) { @@ -131,23 +70,64 @@ namespace MediaBrowser.Providers.MediaInfo if (language != fileNameWithoutExtension) { - var culture = localizationManager.FindLanguageInfo(language); + var culture = _localizationManager.FindLanguageInfo(language); language = culture == null ? language : culture.ThreeLetterISOLanguageName; mediaStream.Language = language; } } - streams.Add(mediaStream); + yield return mediaStream; } } } - private Task GetMediaInfo(string path, IMediaEncoder mediaEncoder, CancellationToken cancellationToken) + public IEnumerable GetExternalAudioFiles( + Video video, + IDirectoryService directoryService, + bool clearCache) + { + if (!video.IsFileProtocol) + { + yield break; + } + + // Check if video folder exists + string folder = video.ContainingFolderPath; + if (!Directory.Exists(folder)) + { + yield break; + } + + string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + + var files = directoryService.GetFilePaths(folder, clearCache, true); + for (int i = 0; i < files.Count; i++) + { + string file = files[i]; + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); + + if (!AudioFileParser.IsAudioFile(file, _namingOptions)) + { + continue; + } + + // The audio 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) + || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) + { + yield return file; + } + } + } + + private Task GetMediaInfo(string path, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return mediaEncoder.GetMediaInfo( + return _mediaEncoder.GetMediaInfo( new MediaInfoRequest { MediaType = DlnaProfileType.Audio, diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 98909c94ec..392641468e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -50,10 +50,9 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; - private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); - private readonly NamingOptions _namingOptions; + private readonly AudioResolver _audioResolver; public FFProbeProvider( ILogger logger, @@ -83,6 +82,7 @@ namespace MediaBrowser.Providers.MediaInfo _namingOptions = namingOptions; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + _audioResolver = new AudioResolver(_localization, _mediaEncoder, namingOptions); } public string Name => "ffprobe"; @@ -102,7 +102,7 @@ namespace MediaBrowser.Providers.MediaInfo var file = directoryService.GetFile(path); if (file != null && file.LastWriteTimeUtc != item.DateModified) { - _logger.LogDebug("Refreshing {0} due to date modified timestamp change.", path); + _logger.LogDebug("Refreshing {ItemPath} due to date modified timestamp change.", path); return true; } } @@ -112,16 +112,15 @@ namespace MediaBrowser.Providers.MediaInfo && !video.SubtitleFiles.SequenceEqual( _subtitleResolver.GetExternalSubtitleFiles(video, directoryService, false), StringComparer.Ordinal)) { - _logger.LogDebug("Refreshing {0} due to external subtitles change.", item.Path); + _logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path); return true; } - AudioResolver audioResolver = new AudioResolver(); if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder && !video.AudioFiles.SequenceEqual( - audioResolver.GetExternalAudioFiles(video, directoryService, _namingOptions, false), StringComparer.Ordinal)) + _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) { - _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path); + _logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path); return true; } @@ -203,7 +202,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager, _chapterManager, _libraryManager, - _namingOptions); + _audioResolver); return prober.ProbeVideo(item, options, cancellationToken); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 7450205993..b31f0ed23d 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using DvdLib.Ifo; -using Emby.Naming.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -45,7 +44,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; - private readonly NamingOptions _namingOptions; + private readonly AudioResolver _audioResolver; private readonly IMediaSourceManager _mediaSourceManager; private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; @@ -62,7 +61,7 @@ namespace MediaBrowser.Providers.MediaInfo ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager, - NamingOptions namingOptions) + AudioResolver audioResolver) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -74,7 +73,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; _chapterManager = chapterManager; _libraryManager = libraryManager; - _namingOptions = namingOptions; + _audioResolver = audioResolver; _mediaSourceManager = mediaSourceManager; } @@ -218,7 +217,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - await AddExternalAudio(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + await AddExternalAudio(video, mediaStreams, options, cancellationToken); var libraryOptions = _libraryManager.GetLibraryOptions(video); @@ -593,14 +592,16 @@ namespace MediaBrowser.Providers.MediaInfo MetadataRefreshOptions options, CancellationToken cancellationToken) { - var audioResolver = new AudioResolver(); - var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; - var externalAudioStreams = await audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, _namingOptions, false, _localization, _mediaEncoder, cancellationToken); + var externalAudioStreams = _audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false, cancellationToken); - video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray(); + await foreach (MediaStream externalAudioStream in externalAudioStreams) + { + currentStreams.Add(externalAudioStream); + } - currentStreams.AddRange(externalAudioStreams); + // Select all external audio file paths + video.AudioFiles = currentStreams.Where(i => i.Type == MediaStreamType.Audio && i.IsExternal).Select(i => i.Path).Distinct().ToArray(); } /// From b5b994b22f8ec8cf0fd10f67d78d66dd12b3e21c Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Sun, 28 Nov 2021 14:20:12 +0100 Subject: [PATCH 052/167] Fix compiler warning due to missing EnumeratorCancellation attribute --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index d23afdc3b9..869512f76c 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Emby.Naming.Audio; @@ -40,8 +41,11 @@ namespace MediaBrowser.Providers.MediaInfo int startIndex, IDirectoryService directoryService, bool clearCache, - CancellationToken cancellationToken) + [EnumeratorCancellation] CancellationToken cancellationToken) { + + cancellationToken.ThrowIfCancellationRequested(); + if (!video.IsFileProtocol) { yield break; From c61b9ef05a4a49a90378e9f81275eb84c2b55eed Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Tue, 30 Nov 2021 19:52:44 +0100 Subject: [PATCH 053/167] Fix warning due to new line after opening bracket --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 869512f76c..35351665a3 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -43,7 +43,6 @@ namespace MediaBrowser.Providers.MediaInfo bool clearCache, [EnumeratorCancellation] CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); if (!video.IsFileProtocol) From 1a356908346fbfe1ba0cf438b2a5c8248fd3f371 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:44:16 +0100 Subject: [PATCH 054/167] Don't disable warnings in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 35351665a3..795c4d6ce5 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA1002, CS1591 - using System; using System.Collections.Generic; using System.IO; From 0d8170cedb1fd711e5c2d09d458467cccdd7874a Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:44:57 +0100 Subject: [PATCH 055/167] Move variable in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 795c4d6ce5..6d1486698f 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -106,13 +106,12 @@ namespace MediaBrowser.Providers.MediaInfo for (int i = 0; i < files.Count; i++) { string file = files[i]; - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); - if (!AudioFileParser.IsAudioFile(file, _namingOptions)) { continue; } + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); // The audio 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) || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length From a9a53dc6579184edfb70ce289c06176e769d5280 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:45:21 +0100 Subject: [PATCH 056/167] Add ConfigureAwait true in MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index b31f0ed23d..542e56256e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -217,7 +217,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - await AddExternalAudio(video, mediaStreams, options, cancellationToken); + await AddExternalAudio(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); var libraryOptions = _libraryManager.GetLibraryOptions(video); From 7b500480201cef84f5d794c9f6319cccf8b7b03b Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:45:47 +0100 Subject: [PATCH 057/167] Add ConfigureAwait true in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 6d1486698f..b6f3ac1cc1 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.Providers.MediaInfo foreach (string path in paths) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); - Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken); + Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken).ConfigureAwait(false); foreach (MediaStream mediaStream in mediaInfo.MediaStreams) { From 6bbfcf1906b7a35da3db4954f9a3762bca9f3a93 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Tue, 30 Nov 2021 21:05:43 +0100 Subject: [PATCH 058/167] Add documentation to AudioResolver class --- .../MediaInfo/AudioResolver.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index b6f3ac1cc1..20dee834ff 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -18,12 +18,21 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Providers.MediaInfo { + /// + /// Resolves external audios for videos. + /// public class AudioResolver { private readonly ILocalizationManager _localizationManager; private readonly IMediaEncoder _mediaEncoder; private readonly NamingOptions _namingOptions; + /// + /// Initializes a new instance of the class. + /// + /// The localization manager. + /// The media encoder. + /// The naming options. public AudioResolver( ILocalizationManager localizationManager, IMediaEncoder mediaEncoder, @@ -34,6 +43,15 @@ namespace MediaBrowser.Providers.MediaInfo _namingOptions = namingOptions; } + /// + /// Returns the audio streams found in the external audio files for the given video. + /// + /// The video to get the external audio streams from. + /// The stream index to start adding audio streams at. + /// The directory service to search for files. + /// True if the directory service cache should be cleared before searching. + /// The cancellation token to cancel operation. + /// A list of external audio streams. public async IAsyncEnumerable GetExternalAudioStreams( Video video, int startIndex, @@ -83,6 +101,13 @@ namespace MediaBrowser.Providers.MediaInfo } } + /// + /// Returns the external audio file paths for the given video. + /// + /// The video to get the external audio file paths from. + /// The directory service to search for files. + /// True if the directory service cache should be cleared before searching. + /// A list of external audio file paths. public IEnumerable GetExternalAudioFiles( Video video, IDirectoryService directoryService, @@ -123,6 +148,12 @@ namespace MediaBrowser.Providers.MediaInfo } } + /// + /// Returns the media info of the given audio file. + /// + /// The path to the audio file. + /// The cancellation token to cancel operation. + /// The media info for the given audio file. private Task GetMediaInfo(string path, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); From a0df79d8a54ff129047cb0bb5e9c8f15df3ce55f Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 30 Nov 2021 12:25:45 +0000 Subject: [PATCH 059/167] Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/ --- Emby.Server.Implementations/Localization/Core/fa.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 8ab657e5b8..3d3b3533fc 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -6,7 +6,7 @@ "AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد", "Books": "کتاب‌ها", "CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}", - "Channels": "کانال‌ها", + "Channels": "کانالها", "ChapterNameValue": "قسمت {0}", "Collections": "مجموعه‌ها", "DeviceOfflineWithName": "ارتباط {0} قطع شد", @@ -37,7 +37,7 @@ "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", "MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد", "MixedContent": "محتوای مخلوط", - "Movies": "فیلم‌ها", + "Movies": "فیلم ها", "Music": "موسیقی", "MusicVideos": "موزیک ویدیوها", "NameInstallFailed": "{0} نصب با مشکل مواجه شد", From 2da7777e6dc25e7325a45ba2262ef4327e4ee4b6 Mon Sep 17 00:00:00 2001 From: snieguzary Date: Mon, 29 Nov 2021 13:06:49 +0000 Subject: [PATCH 060/167] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pl/ --- Emby.Server.Implementations/Localization/Core/pl.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index e8a32a13e8..4fa8d2bb46 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -15,7 +15,7 @@ "Favorites": "Ulubione", "Folders": "Foldery", "Genres": "Gatunki", - "HeaderAlbumArtists": "Album artysty", + "HeaderAlbumArtists": "Wykonawcy albumów", "HeaderContinueWatching": "Kontynuuj odtwarzanie", "HeaderFavoriteAlbums": "Ulubione albumy", "HeaderFavoriteArtists": "Ulubieni wykonawcy", @@ -47,7 +47,7 @@ "NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji", "NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację", "NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki", - "NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane", + "NotificationOptionAudioPlaybackStopped": "Odtwarzanie dźwięku zatrzymane", "NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego", "NotificationOptionInstallationFailed": "Nieudana instalacja", "NotificationOptionNewLibraryContent": "Dodano nową zawartość", @@ -98,7 +98,7 @@ "TaskRefreshChannels": "Odśwież kanały", "TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.", "TaskCleanTranscode": "Wyczyść folder transkodowania", - "TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów które są skonfigurowane do automatycznej aktualizacji.", + "TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów, które są skonfigurowane do automatycznej aktualizacji.", "TaskUpdatePlugins": "Aktualizuj pluginy", "TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.", "TaskRefreshPeople": "Odśwież obsadę", From f6d8c19a7ac41c6c7c217d9e9ccbf98f78122327 Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Tue, 30 Nov 2021 06:43:44 +0000 Subject: [PATCH 061/167] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index cedf468f72..b7ece8d5fc 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -103,7 +103,7 @@ "HeaderFavoriteEpisodes": "Tập Phim Yêu Thích", "HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích", "HeaderFavoriteAlbums": "Album Ưa Thích", - "FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}", + "FailedLoginAttemptWithUserName": "Đăng nhập không thành công thử từ {0}", "DeviceOnlineWithName": "{0} đã kết nối", "DeviceOfflineWithName": "{0} đã ngắt kết nối", "ChapterNameValue": "Phân Cảnh {0}", From beafd6eaabf7baa7c478b4a0ef131ddc480b368a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 1 Dec 2021 00:13:02 +0100 Subject: [PATCH 062/167] Use JsonContent where possible Should reduce the # of allocated bytes --- .../LiveTv/Listings/SchedulesDirect.cs | 9 ++++----- MediaBrowser.Model/Configuration/LibraryOptions.cs | 1 + tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs | 6 +++--- .../Controllers/DlnaControllerTests.cs | 10 ++++------ .../Controllers/MediaStructureControllerTests.cs | 7 +++---- .../Controllers/StartupControllerTests.cs | 7 +++---- .../Controllers/UserControllerTests.cs | 7 +++---- 7 files changed, 21 insertions(+), 26 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 08aa0cfd72..93d72dba44 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -9,6 +9,7 @@ using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Json; using System.Net.Http.Headers; using System.Net.Mime; using System.Security.Cryptography; @@ -101,11 +102,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings } }; - var requestString = JsonSerializer.Serialize(requestList, _jsonOptions); - _logger.LogDebug("Request string for schedules is: {RequestString}", requestString); + _logger.LogDebug("Request string for schedules is: {@RequestString}", requestList); using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules"); - options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json); + options.Content = JsonContent.Create(requestList, options: _jsonOptions); options.Headers.TryAddWithoutValidation("token", token); using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); @@ -121,8 +121,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings programRequestOptions.Headers.TryAddWithoutValidation("token", token); var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); - programRequestOptions.Content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(programIds, _jsonOptions)); - programRequestOptions.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions); using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 90cf8f43bd..ef049af4b0 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -81,6 +81,7 @@ namespace MediaBrowser.Model.Configuration public bool RequirePerfectSubtitleMatch { get; set; } public bool SaveSubtitlesWithMedia { get; set; } + public bool AutomaticallyAddToCollection { get; set; } public TypeOptions[] TypeOptions { get; set; } diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs index 4ea05397dd..4c8f64d1e9 100644 --- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs +++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs @@ -1,6 +1,7 @@ using System; using System.Net; using System.Net.Http; +using System.Net.Http.Json; using System.Net.Http.Headers; using System.Net.Mime; using System.Text.Json; @@ -26,14 +27,13 @@ namespace Jellyfin.Server.Integration.Tests using var completeResponse = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty())).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NoContent, completeResponse.StatusCode); - using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes( + using var content = JsonContent.Create( new AuthenticateUserByName() { Username = user!.Name, Pw = user.Password, }, - jsonOptions)); - content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + options: jsonOptions); content.Headers.Add("X-Emby-Authorization", DummyAuthHeader); using var authResponse = await client.PostAsync("/Users/AuthenticateByName", content).ConfigureAwait(false); diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs index 4421ced727..8a03583bb2 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Json; using System.Net.Http.Headers; using System.Net.Mime; using System.Text; @@ -62,8 +63,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Name = "ThisProfileDoesNotExist" }; - using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(deviceProfile, _jsonOptions)); - content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var content = JsonContent.Create(deviceProfile, options: _jsonOptions); using var getResponse = await client.PostAsync("/Dlna/Profiles/" + NonExistentProfile, content).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode); } @@ -80,8 +80,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Name = "ThisProfileIsNew" }; - using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(deviceProfile, _jsonOptions)); - content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var content = JsonContent.Create(deviceProfile, options: _jsonOptions); using var getResponse = await client.PostAsync("/Dlna/Profiles", content).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode); } @@ -120,8 +119,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Id = _newDeviceProfileId }; - using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(updatedProfile, _jsonOptions)); - content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var content = JsonContent.Create(updatedProfile, options: _jsonOptions); using var getResponse = await client.PostAsync("/Dlna/Profiles", content).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode); } diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs index 19d8381ea5..a9713b4cb8 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs @@ -1,6 +1,7 @@ using System; using System.Net; using System.Net.Http; +using System.Net.Http.Json; using System.Net.Http.Headers; using System.Net.Mime; using System.Text.Json; @@ -71,8 +72,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Path = "/this/path/doesnt/exist" }; - using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions)); - postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var postContent = JsonContent.Create(data, options: _jsonOptions); var response = await client.PostAsync("Library/VirtualFolders/Paths", postContent).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); @@ -90,8 +90,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers PathInfo = new MediaPathInfo("test") }; - using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions)); - postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var postContent = JsonContent.Create(data, options: _jsonOptions); var response = await client.PostAsync("Library/VirtualFolders/Paths/Update", postContent).ConfigureAwait(false); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs index 9c0fc72f65..d9cde1dd96 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs @@ -1,6 +1,7 @@ using System; using System.Net; using System.Net.Http; +using System.Net.Http.Json; using System.Net.Http.Headers; using System.Net.Mime; using System.Text.Json; @@ -36,8 +37,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers PreferredMetadataLanguage = "nl" }; - using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(config, _jsonOptions)); - postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var postContent = JsonContent.Create(config, options: _jsonOptions); using var postResponse = await client.PostAsync("/Startup/Configuration", postContent).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode); @@ -80,8 +80,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Password = "NewPassword" }; - using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions)); - postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var postContent = JsonContent.Create(user, options: _jsonOptions); var postResponse = await client.PostAsync("/Startup/User", postContent).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode); diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs index 8866ab53cd..0f8b930f14 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Json; using System.Net.Http.Headers; using System.Net.Mime; using System.Text.Json; @@ -32,15 +33,13 @@ namespace Jellyfin.Server.Integration.Tests.Controllers private Task CreateUserByName(HttpClient httpClient, CreateUserByName request) { - using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions)); - postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var postContent = JsonContent.Create(request, options: _jsonOpions); return httpClient.PostAsync("Users/New", postContent); } private Task UpdateUserPassword(HttpClient httpClient, Guid userId, UpdateUserPassword request) { - using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions)); - postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var postContent = JsonContent.Create(request, options: _jsonOpions); return httpClient.PostAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", postContent); } From 40be86eec0fa7f43b64d05f30adcfab88ad3c402 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 1 Dec 2021 15:58:23 +0100 Subject: [PATCH 063/167] Use PostAsJsonAsync where possible --- .../Controllers/DlnaControllerTests.cs | 9 +++------ .../Controllers/MediaStructureControllerTests.cs | 6 ++---- .../Controllers/StartupControllerTests.cs | 6 ++---- .../Controllers/UserControllerTests.cs | 10 ++-------- 4 files changed, 9 insertions(+), 22 deletions(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs index 8a03583bb2..4c46933aab 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs @@ -63,8 +63,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Name = "ThisProfileDoesNotExist" }; - using var content = JsonContent.Create(deviceProfile, options: _jsonOptions); - using var getResponse = await client.PostAsync("/Dlna/Profiles/" + NonExistentProfile, content).ConfigureAwait(false); + using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles/" + NonExistentProfile, deviceProfile, _jsonOptions).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode); } @@ -80,8 +79,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Name = "ThisProfileIsNew" }; - using var content = JsonContent.Create(deviceProfile, options: _jsonOptions); - using var getResponse = await client.PostAsync("/Dlna/Profiles", content).ConfigureAwait(false); + using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles", deviceProfile, _jsonOptions).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode); } @@ -119,8 +117,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Id = _newDeviceProfileId }; - using var content = JsonContent.Create(updatedProfile, options: _jsonOptions); - using var getResponse = await client.PostAsync("/Dlna/Profiles", content).ConfigureAwait(false); + using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles", updatedProfile, _jsonOptions).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode); } diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs index a9713b4cb8..2da5237db2 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs @@ -72,8 +72,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Path = "/this/path/doesnt/exist" }; - using var postContent = JsonContent.Create(data, options: _jsonOptions); - var response = await client.PostAsync("Library/VirtualFolders/Paths", postContent).ConfigureAwait(false); + var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths", data, _jsonOptions).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } @@ -90,8 +89,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers PathInfo = new MediaPathInfo("test") }; - using var postContent = JsonContent.Create(data, options: _jsonOptions); - var response = await client.PostAsync("Library/VirtualFolders/Paths/Update", postContent).ConfigureAwait(false); + var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths/Update", data, _jsonOptions).ConfigureAwait(false); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs index d9cde1dd96..ed92ce25a4 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs @@ -37,8 +37,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers PreferredMetadataLanguage = "nl" }; - using var postContent = JsonContent.Create(config, options: _jsonOptions); - using var postResponse = await client.PostAsync("/Startup/Configuration", postContent).ConfigureAwait(false); + using var postResponse = await client.PostAsJsonAsync("/Startup/Configuration", config, _jsonOptions).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode); using var getResponse = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false); @@ -80,8 +79,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Password = "NewPassword" }; - using var postContent = JsonContent.Create(user, options: _jsonOptions); - var postResponse = await client.PostAsync("/Startup/User", postContent).ConfigureAwait(false); + var postResponse = await client.PostAsJsonAsync("/Startup/User", user, _jsonOptions).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode); var getResponse = await client.GetAsync("/Startup/User").ConfigureAwait(false); diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs index 0f8b930f14..f11f276f80 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs @@ -32,16 +32,10 @@ namespace Jellyfin.Server.Integration.Tests.Controllers } private Task CreateUserByName(HttpClient httpClient, CreateUserByName request) - { - using var postContent = JsonContent.Create(request, options: _jsonOpions); - return httpClient.PostAsync("Users/New", postContent); - } + => httpClient.PostAsJsonAsync("Users/New", request, _jsonOpions); private Task UpdateUserPassword(HttpClient httpClient, Guid userId, UpdateUserPassword request) - { - using var postContent = JsonContent.Create(request, options: _jsonOpions); - return httpClient.PostAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", postContent); - } + => httpClient.PostAsJsonAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", request, _jsonOpions); [Fact] [Priority(-1)] From 180e2dc329434a68a5fe3a8ac05591d0a7c898f8 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Wed, 1 Dec 2021 21:05:43 +0100 Subject: [PATCH 064/167] Prevent crashes in specific scenarios --- MediaBrowser.Controller/Entities/Video.cs | 1 + MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 56e955adad..8e0593507a 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -33,6 +33,7 @@ namespace MediaBrowser.Controller.Entities AdditionalParts = Array.Empty(); LocalAlternateVersions = Array.Empty(); SubtitleFiles = Array.Empty(); + AudioFiles = Array.Empty(); LinkedAlternateVersions = Array.Empty(); } diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index 20dee834ff..bec8ee34a6 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -131,7 +131,7 @@ namespace MediaBrowser.Providers.MediaInfo for (int i = 0; i < files.Count; i++) { string file = files[i]; - if (!AudioFileParser.IsAudioFile(file, _namingOptions)) + if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) || !AudioFileParser.IsAudioFile(file, _namingOptions)) { continue; } From 5535b9c01f56bb789ae1125e64d9515019a052df Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 2 Dec 2021 11:21:59 +0100 Subject: [PATCH 065/167] Reduce allocations --- .../MediaInfo/FFProbeProvider.cs | 54 ++++++------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index d4b5d8655c..887a7f80cf 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -38,17 +38,9 @@ namespace MediaBrowser.Providers.MediaInfo IHasItemChangeMonitor { private readonly ILogger _logger; - private readonly IMediaEncoder _mediaEncoder; - private readonly IItemRepository _itemRepo; - private readonly IBlurayExaminer _blurayExaminer; - private readonly ILocalizationManager _localization; - private readonly IEncodingManager _encodingManager; - private readonly IServerConfigurationManager _config; - private readonly ISubtitleManager _subtitleManager; - private readonly IChapterManager _chapterManager; - private readonly ILibraryManager _libraryManager; - private readonly IMediaSourceManager _mediaSourceManager; private readonly SubtitleResolver _subtitleResolver; + private readonly FFProbeVideoInfo _videoProber; + private readonly FFProbeAudioInfo _audioProber; private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); @@ -66,18 +58,21 @@ namespace MediaBrowser.Providers.MediaInfo ILibraryManager libraryManager) { _logger = logger; - _mediaEncoder = mediaEncoder; - _itemRepo = itemRepo; - _blurayExaminer = blurayExaminer; - _localization = localization; - _encodingManager = encodingManager; - _config = config; - _subtitleManager = subtitleManager; - _chapterManager = chapterManager; - _libraryManager = libraryManager; - _mediaSourceManager = mediaSourceManager; _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + _videoProber = new FFProbeVideoInfo( + _logger, + mediaSourceManager, + mediaEncoder, + itemRepo, + blurayExaminer, + localization, + encodingManager, + config, + subtitleManager, + chapterManager, + libraryManager); + _audioProber = new FFProbeAudioInfo(mediaSourceManager, mediaEncoder, itemRepo, libraryManager); } public string Name => "ffprobe"; @@ -177,20 +172,7 @@ namespace MediaBrowser.Providers.MediaInfo FetchShortcutInfo(item); } - var prober = new FFProbeVideoInfo( - _logger, - _mediaSourceManager, - _mediaEncoder, - _itemRepo, - _blurayExaminer, - _localization, - _encodingManager, - _config, - _subtitleManager, - _chapterManager, - _libraryManager); - - return prober.ProbeVideo(item, options, cancellationToken); + return _videoProber.ProbeVideo(item, options, cancellationToken); } private string NormalizeStrmLine(string line) @@ -226,9 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo FetchShortcutInfo(item); } - var prober = new FFProbeAudioInfo(_mediaSourceManager, _mediaEncoder, _itemRepo, _libraryManager); - - return prober.Probe(item, options, cancellationToken); + return _audioProber.Probe(item, options, cancellationToken); } } } From 120828d8d03da08a55edfbce5bfe1463bf06eae3 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Fri, 3 Dec 2021 19:18:43 +0100 Subject: [PATCH 066/167] Replace escaped quote string with quote character in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs Co-authored-by: Cody Robibero --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5712303123..92b345f126 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -698,7 +698,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null && state.AudioStream.IsExternal) { - arg.Append(" -i \"").Append(state.AudioStream.Path).Append("\""); + arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"'); } return arg.ToString(); From 99a48554a618302b4fe70ef1fd3d7fd06096c70e Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Fri, 3 Dec 2021 19:19:22 +0100 Subject: [PATCH 067/167] Optimize calculation of external audio stream index in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs Co-authored-by: Cody Robibero --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 92b345f126..91f6654bb7 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2007,7 +2007,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream.IsExternal) { int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; - int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); + int externalAudioStream = state.MediaSource.MediaStreams.FindIndex(i => i.Path == state.AudioStream.Path); args += string.Format( CultureInfo.InvariantCulture, From 6193fdea698ad4486140bc6a00a9547ab979c8f7 Mon Sep 17 00:00:00 2001 From: Ahmed Rafiq Date: Sat, 4 Dec 2021 19:50:48 +0600 Subject: [PATCH 068/167] Use MimeTypes package to determine MIME type This simplifies the code since we don't have to keep large mappings of extensions and MIME types. We still keep the ability to override the mappings for: - filling in entries not present in the package, for e.g. ".azw3" - picking preferred extensions, for e.g. MimeTypes provides ".conf" as a possible extionsion for "text/plain", and while that is correct, ".txt" would be preferrable - compatibility reasons --- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 + MediaBrowser.Model/Net/MimeTypes.cs | 147 ++++++---------- .../Net/MimeTypesTests.cs | 164 ++++++++++++++++++ 3 files changed, 217 insertions(+), 98 deletions(-) create mode 100644 tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 70fef5d661..b1fbe864b4 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -31,6 +31,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 043cee2a25..ff8d1fbae5 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -12,6 +12,10 @@ namespace MediaBrowser.Model.Net /// /// Class MimeTypes. /// + /// + /// http://en.wikipedia.org/wiki/Internet_media_type + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + /// http://www.iana.org/assignments/media-types/media-types.xhtml public static class MimeTypes { /// @@ -50,81 +54,26 @@ namespace MediaBrowser.Model.Net ".wtv", }; - // http://en.wikipedia.org/wiki/Internet_media_type - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types - // http://www.iana.org/assignments/media-types/media-types.xhtml - // Add more as needed + /// + /// Used for extensions not in or to override them. + /// private static readonly Dictionary _mimeTypeLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) { // Type application - { ".7z", "application/x-7z-compressed" }, - { ".azw", "application/vnd.amazon.ebook" }, { ".azw3", "application/vnd.amazon.ebook" }, - { ".cbz", "application/x-cbz" }, - { ".cbr", "application/epub+zip" }, - { ".eot", "application/vnd.ms-fontobject" }, - { ".epub", "application/epub+zip" }, - { ".js", "application/x-javascript" }, - { ".json", "application/json" }, - { ".m3u8", "application/x-mpegURL" }, - { ".map", "application/x-javascript" }, - { ".mobi", "application/x-mobipocket-ebook" }, - { ".opf", "application/oebps-package+xml" }, - { ".pdf", "application/pdf" }, - { ".rar", "application/vnd.rar" }, - { ".srt", "application/x-subrip" }, - { ".ttml", "application/ttml+xml" }, - { ".wasm", "application/wasm" }, - { ".xml", "application/xml" }, - { ".zip", "application/zip" }, // Type image - { ".bmp", "image/bmp" }, - { ".gif", "image/gif" }, - { ".ico", "image/vnd.microsoft.icon" }, - { ".jpg", "image/jpeg" }, - { ".jpeg", "image/jpeg" }, - { ".png", "image/png" }, - { ".svg", "image/svg+xml" }, - { ".svgz", "image/svg+xml" }, { ".tbn", "image/jpeg" }, - { ".tif", "image/tiff" }, - { ".tiff", "image/tiff" }, - { ".webp", "image/webp" }, - - // Type font - { ".ttf", "font/ttf" }, - { ".woff", "font/woff" }, - { ".woff2", "font/woff2" }, // Type text { ".ass", "text/x-ssa" }, { ".ssa", "text/x-ssa" }, - { ".css", "text/css" }, - { ".csv", "text/csv" }, { ".edl", "text/plain" }, - { ".rtf", "text/rtf" }, - { ".txt", "text/plain" }, - { ".vtt", "text/vtt" }, + { ".html", "text/html; charset=UTF-8" }, + { ".htm", "text/html; charset=UTF-8" }, // Type video - { ".3gp", "video/3gpp" }, - { ".3g2", "video/3gpp2" }, - { ".asf", "video/x-ms-asf" }, - { ".avi", "video/x-msvideo" }, - { ".flv", "video/x-flv" }, - { ".mp4", "video/mp4" }, - { ".m4s", "video/mp4" }, - { ".m4v", "video/x-m4v" }, { ".mpegts", "video/mp2t" }, - { ".mpg", "video/mpeg" }, - { ".mkv", "video/x-matroska" }, - { ".mov", "video/quicktime" }, - { ".mpd", "video/vnd.mpeg.dash.mpd" }, - { ".ogv", "video/ogg" }, - { ".ts", "video/mp2t" }, - { ".webm", "video/webm" }, - { ".wmv", "video/x-ms-wmv" }, // Type audio { ".aac", "audio/aac" }, @@ -133,37 +82,47 @@ namespace MediaBrowser.Model.Net { ".dsf", "audio/dsf" }, { ".dsp", "audio/dsp" }, { ".flac", "audio/flac" }, - { ".m4a", "audio/mp4" }, { ".m4b", "audio/m4b" }, - { ".mid", "audio/midi" }, - { ".midi", "audio/midi" }, { ".mp3", "audio/mpeg" }, - { ".oga", "audio/ogg" }, - { ".ogg", "audio/ogg" }, - { ".opus", "audio/ogg" }, { ".vorbis", "audio/vorbis" }, - { ".wav", "audio/wav" }, { ".webma", "audio/webm" }, - { ".wma", "audio/x-ms-wma" }, { ".wv", "audio/x-wavpack" }, { ".xsp", "audio/xsp" }, }; - private static readonly Dictionary _extensionLookup = CreateExtensionLookup(); - - private static Dictionary CreateExtensionLookup() + private static readonly Dictionary _extensionLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) { - var dict = _mimeTypeLookup - .GroupBy(i => i.Value) - .ToDictionary(x => x.Key, x => x.First().Key, StringComparer.OrdinalIgnoreCase); + // Type application + { "application/x-cbz", ".cbz" }, + { "application/x-javascript", ".js" }, + { "application/xml", ".xml" }, + { "application/x-mpegURL", ".m3u8" }, - dict["image/jpg"] = ".jpg"; - dict["image/x-png"] = ".png"; + // Type audio + { "audio/aac", ".aac" }, + { "audio/ac3", ".ac3" }, + { "audio/dsf", ".dsf" }, + { "audio/dsp", ".dsp" }, + { "audio/flac", ".flac" }, + { "audio/m4b", ".m4b" }, + { "audio/vorbis", ".vorbis" }, + { "audio/x-ape", ".ape" }, + { "audio/xsp", ".xsp" }, + { "audio/x-wavpack", ".wv" }, - dict["audio/x-aac"] = ".aac"; + // Type image + { "image/jpg", ".jpg" }, + { "image/x-png", ".png" }, - return dict; - } + // Type text + { "text/plain", ".txt" }, + { "text/rtf", ".rtf" }, + { "text/x-ssa", ".ssa" }, + + // Type video + { "video/vnd.mpeg.dash.mpd", ".mpd" }, + { "video/x-matroska", ".mkv" }, + }; public static string GetMimeType(string path) => GetMimeType(path, "application/octet-stream"); @@ -188,31 +147,17 @@ namespace MediaBrowser.Model.Net return result; } + if (Model.MimeTypes.TryGetMimeType(filename, out var mimeType)) + { + return mimeType; + } + // Catch-all for all video types that don't require specific mime types if (_videoFileExtensions.Contains(ext)) { return string.Concat("video/", ext.AsSpan(1)); } - // Type text - if (string.Equals(ext, ".html", StringComparison.OrdinalIgnoreCase) - || string.Equals(ext, ".htm", StringComparison.OrdinalIgnoreCase)) - { - return "text/html; charset=UTF-8"; - } - - if (string.Equals(ext, ".log", StringComparison.OrdinalIgnoreCase) - || string.Equals(ext, ".srt", StringComparison.OrdinalIgnoreCase)) - { - return "text/plain"; - } - - // Misc - if (string.Equals(ext, ".dll", StringComparison.OrdinalIgnoreCase)) - { - return "application/octet-stream"; - } - return defaultValue; } @@ -231,6 +176,12 @@ namespace MediaBrowser.Model.Net return result; } + var extensions = Model.MimeTypes.GetMimeTypeExtensions(mimeType); + if (extensions.Any()) + { + return "." + extensions.First(); + } + return null; } } diff --git a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs new file mode 100644 index 0000000000..b82607bf0e --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Net; +using Xunit; + +namespace Jellyfin.Model.Tests.Net +{ + public class MimeTypesTests + { + [Theory] + [InlineData(".dll", "application/octet-stream")] + [InlineData(".log", "text/plain")] + [InlineData(".srt", "application/x-subrip")] + [InlineData(".html", "text/html; charset=UTF-8")] + [InlineData(".htm", "text/html; charset=UTF-8")] + [InlineData(".7z", "application/x-7z-compressed")] + [InlineData(".azw", "application/vnd.amazon.ebook")] + [InlineData(".azw3", "application/vnd.amazon.ebook")] + [InlineData(".eot", "application/vnd.ms-fontobject")] + [InlineData(".epub", "application/epub+zip")] + [InlineData(".json", "application/json")] + [InlineData(".mobi", "application/x-mobipocket-ebook")] + [InlineData(".opf", "application/oebps-package+xml")] + [InlineData(".pdf", "application/pdf")] + [InlineData(".rar", "application/vnd.rar")] + [InlineData(".ttml", "application/ttml+xml")] + [InlineData(".wasm", "application/wasm")] + [InlineData(".xml", "application/xml")] + [InlineData(".zip", "application/zip")] + [InlineData(".bmp", "image/bmp")] + [InlineData(".gif", "image/gif")] + [InlineData(".ico", "image/vnd.microsoft.icon")] + [InlineData(".jpg", "image/jpeg")] + [InlineData(".jpeg", "image/jpeg")] + [InlineData(".png", "image/png")] + [InlineData(".svg", "image/svg+xml")] + [InlineData(".svgz", "image/svg+xml")] + [InlineData(".tbn", "image/jpeg")] + [InlineData(".tif", "image/tiff")] + [InlineData(".tiff", "image/tiff")] + [InlineData(".webp", "image/webp")] + [InlineData(".ttf", "font/ttf")] + [InlineData(".woff", "font/woff")] + [InlineData(".woff2", "font/woff2")] + [InlineData(".ass", "text/x-ssa")] + [InlineData(".ssa", "text/x-ssa")] + [InlineData(".css", "text/css")] + [InlineData(".csv", "text/csv")] + [InlineData(".edl", "text/plain")] + [InlineData(".txt", "text/plain")] + [InlineData(".vtt", "text/vtt")] + [InlineData(".3gp", "video/3gpp")] + [InlineData(".3g2", "video/3gpp2")] + [InlineData(".asf", "video/x-ms-asf")] + [InlineData(".avi", "video/x-msvideo")] + [InlineData(".flv", "video/x-flv")] + [InlineData(".mp4", "video/mp4")] + [InlineData(".m4v", "video/x-m4v")] + [InlineData(".mpegts", "video/mp2t")] + [InlineData(".mpg", "video/mpeg")] + [InlineData(".mkv", "video/x-matroska")] + [InlineData(".mov", "video/quicktime")] + [InlineData(".ogv", "video/ogg")] + [InlineData(".ts", "video/mp2t")] + [InlineData(".webm", "video/webm")] + [InlineData(".wmv", "video/x-ms-wmv")] + [InlineData(".aac", "audio/aac")] + [InlineData(".ac3", "audio/ac3")] + [InlineData(".ape", "audio/x-ape")] + [InlineData(".dsf", "audio/dsf")] + [InlineData(".dsp", "audio/dsp")] + [InlineData(".flac", "audio/flac")] + [InlineData(".m4a", "audio/mp4")] + [InlineData(".m4b", "audio/m4b")] + [InlineData(".mid", "audio/midi")] + [InlineData(".midi", "audio/midi")] + [InlineData(".mp3", "audio/mpeg")] + [InlineData(".oga", "audio/ogg")] + [InlineData(".ogg", "audio/ogg")] + [InlineData(".opus", "audio/ogg")] + [InlineData(".vorbis", "audio/vorbis")] + [InlineData(".wav", "audio/wav")] + [InlineData(".webma", "audio/webm")] + [InlineData(".wma", "audio/x-ms-wma")] + [InlineData(".wv", "audio/x-wavpack")] + [InlineData(".xsp", "audio/xsp")] + public void GetMimeType(string input, string expectedResult) + { + Assert.Equal(expectedResult, MimeTypes.GetMimeType(input, null)); + } + + [Theory] + [InlineData("application/epub+zip", ".epub")] + [InlineData("application/json", ".json")] + [InlineData("application/oebps-package+xml", ".opf")] + [InlineData("application/pdf", ".pdf")] + [InlineData("application/ttml+xml", ".ttml")] + [InlineData("application/vnd.amazon.ebook", ".azw")] + [InlineData("application/vnd.ms-fontobject", ".eot")] + [InlineData("application/vnd.rar", ".rar")] + [InlineData("application/wasm", ".wasm")] + [InlineData("application/x-7z-compressed", ".7z")] + [InlineData("application/x-cbz", ".cbz")] + [InlineData("application/x-javascript", ".js")] + [InlineData("application/x-mobipocket-ebook", ".mobi")] + [InlineData("application/x-mpegURL", ".m3u8")] + [InlineData("application/x-subrip", ".srt")] + [InlineData("application/xml", ".xml")] + [InlineData("application/zip", ".zip")] + [InlineData("audio/aac", ".aac")] + [InlineData("audio/ac3", ".ac3")] + [InlineData("audio/dsf", ".dsf")] + [InlineData("audio/dsp", ".dsp")] + [InlineData("audio/flac", ".flac")] + [InlineData("audio/m4b", ".m4b")] + [InlineData("audio/mp4", ".m4a")] + [InlineData("audio/vorbis", ".vorbis")] + [InlineData("audio/wav", ".wav")] + [InlineData("audio/x-aac", ".aac")] + [InlineData("audio/x-ape", ".ape")] + [InlineData("audio/x-ms-wma", ".wma")] + [InlineData("audio/x-wavpack", ".wv")] + [InlineData("audio/xsp", ".xsp")] + [InlineData("font/ttf", ".ttf")] + [InlineData("font/woff", ".woff")] + [InlineData("font/woff2", ".woff2")] + [InlineData("image/bmp", ".bmp")] + [InlineData("image/gif", ".gif")] + [InlineData("image/jpg", ".jpg")] + [InlineData("image/png", ".png")] + [InlineData("image/svg+xml", ".svg")] + [InlineData("image/tiff", ".tif")] + [InlineData("image/vnd.microsoft.icon", ".ico")] + [InlineData("image/webp", ".webp")] + [InlineData("image/x-png", ".png")] + [InlineData("text/css", ".css")] + [InlineData("text/csv", ".csv")] + [InlineData("text/plain", ".txt")] + [InlineData("text/rtf", ".rtf")] + [InlineData("text/vtt", ".vtt")] + [InlineData("text/x-ssa", ".ssa")] + [InlineData("video/3gpp", ".3gp")] + [InlineData("video/3gpp2", ".3g2")] + [InlineData("video/mp2t", ".ts")] + [InlineData("video/mp4", ".mp4")] + [InlineData("video/ogg", ".ogv")] + [InlineData("video/quicktime", ".mov")] + [InlineData("video/vnd.mpeg.dash.mpd", ".mpd")] + [InlineData("video/webm", ".webm")] + [InlineData("video/x-flv", ".flv")] + [InlineData("video/x-m4v", ".m4v")] + [InlineData("video/x-matroska", ".mkv")] + [InlineData("video/x-ms-asf", ".asf")] + [InlineData("video/x-ms-wmv", ".wmv")] + [InlineData("video/x-msvideo", ".avi")] + public void ToExtension(string input, string expectedResult) + { + Assert.Equal(expectedResult, MimeTypes.ToExtension(input)); + } + } +} From 40e05a7993599c43b58acb34f59ca5149dc76040 Mon Sep 17 00:00:00 2001 From: Ahmed Rafiq Date: Sat, 4 Dec 2021 21:06:51 +0600 Subject: [PATCH 069/167] Update documentation --- MediaBrowser.Model/Net/MimeTypes.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index ff8d1fbae5..18a3f82599 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -13,9 +13,14 @@ namespace MediaBrowser.Model.Net /// Class MimeTypes. /// /// - /// http://en.wikipedia.org/wiki/Internet_media_type - /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types - /// http://www.iana.org/assignments/media-types/media-types.xhtml + /// + /// For more information on MIME types: + /// + /// http://en.wikipedia.org/wiki/Internet_media_type + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + /// http://www.iana.org/assignments/media-types/media-types.xhtml + /// + /// public static class MimeTypes { /// From 0e491025aec8ef11acf86b189b5e16692fb8d4a6 Mon Sep 17 00:00:00 2001 From: Ahmed Rafiq Date: Sat, 4 Dec 2021 21:13:18 +0600 Subject: [PATCH 070/167] Refactor MimeTypes.ToExtension() Co-authored-by: Cody Robibero --- MediaBrowser.Model/Net/MimeTypes.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 18a3f82599..506e8e9d63 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -181,13 +181,8 @@ namespace MediaBrowser.Model.Net return result; } - var extensions = Model.MimeTypes.GetMimeTypeExtensions(mimeType); - if (extensions.Any()) - { - return "." + extensions.First(); - } - - return null; + var extension = Model.MimeTypes.GetMimeTypeExtensions(mimeType).FirstOrDefault(); + return string.IsNullOrEmpty(extension) ? null : "." + extension; } } } From fa6f6515a6874a5fca4267c50e6c597a2042ad96 Mon Sep 17 00:00:00 2001 From: Ahmed Rafiq Date: Sat, 4 Dec 2021 21:14:16 +0600 Subject: [PATCH 071/167] Update unit test name Co-authored-by: Claus Vium --- tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs index b82607bf0e..637424e27e 100644 --- a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs +++ b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs @@ -87,7 +87,7 @@ namespace Jellyfin.Model.Tests.Net [InlineData(".wma", "audio/x-ms-wma")] [InlineData(".wv", "audio/x-wavpack")] [InlineData(".xsp", "audio/xsp")] - public void GetMimeType(string input, string expectedResult) + public void GetMimeType_Valid_ReturnsCorrectResult(string input, string expectedResult) { Assert.Equal(expectedResult, MimeTypes.GetMimeType(input, null)); } From cf75f99f0e4d765cc7dfa928b3a508121f538db4 Mon Sep 17 00:00:00 2001 From: Ahmed Rafiq Date: Sat, 4 Dec 2021 21:14:52 +0600 Subject: [PATCH 072/167] Update unit test name Co-authored-by: Claus Vium --- tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs index 637424e27e..55050cc954 100644 --- a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs +++ b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs @@ -156,7 +156,7 @@ namespace Jellyfin.Model.Tests.Net [InlineData("video/x-ms-asf", ".asf")] [InlineData("video/x-ms-wmv", ".wmv")] [InlineData("video/x-msvideo", ".avi")] - public void ToExtension(string input, string expectedResult) + public void ToExtension_Valid_ReturnsCorrectResult(string input, string expectedResult) { Assert.Equal(expectedResult, MimeTypes.ToExtension(input)); } From 46543ead27e65bf3bd689883795660da07e99c47 Mon Sep 17 00:00:00 2001 From: archon eleven Date: Mon, 6 Dec 2021 08:45:36 +0000 Subject: [PATCH 073/167] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- .../Localization/Core/ms.json | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 4dcb992930..94ee389d7c 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -37,7 +37,7 @@ "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini", "MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini", "MixedContent": "Kandungan campuran", - "Movies": "Filem", + "Movies": "Filem-filem", "Music": "Muzik", "MusicVideos": "", "NameInstallFailed": "{0} pemasangan gagal", @@ -53,23 +53,23 @@ "NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah", "NotificationOptionPluginError": "Kegagalan plugin", "NotificationOptionPluginInstalled": "Plugin telah dipasang", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", + "NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang", + "NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang", "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", + "NotificationOptionTaskFailed": "Kegagalan tugas berjadual", + "NotificationOptionUserLockedOut": "Pengguna telah dikunci", + "NotificationOptionVideoPlayback": "Ulangmain video bermula", "NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan", "Photos": "Gambar-gambar", "Playlists": "Senarai main", "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", + "PluginInstalledWithName": "{0} telah dipasang", + "PluginUninstalledWithName": "{0} telah dinyahpasang", + "PluginUpdatedWithName": "{0} telah dikemaskini", "ProviderValue": "Provider: {0}", "ScheduledTaskFailedWithName": "{0} gagal", "ScheduledTaskStartedWithName": "{0} bermula", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} perlu di ulangmula", "Shows": "Series", "Songs": "Lagu-lagu", "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", @@ -77,19 +77,19 @@ "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", "Sync": "Sync", "System": "Sistem", - "TvShows": "TV Shows", + "TvShows": "Tayangan TV", "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", + "UserCreatedWithName": "Pengguna {0} telah diwujudkan", + "UserDeletedWithName": "Pengguna {0} telah dipadamkan", + "UserDownloadingItemWithValues": "{0} sedang memuat turun {1}", "UserLockedOutWithName": "Pengguna {0} telah dikunci", "UserOfflineFromDevice": "{0} telah terputus dari {1}", "UserOnlineFromDevice": "{0} berada dalam talian dari {1}", "UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}", "UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {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", + "UserStartedPlayingItemWithValues": "{0} sedang dimainkan {1} pada {2}", + "UserStoppedPlayingItemWithValues": "{0} telah tamat dimainkan {1} pada {2}", + "ValueHasBeenAddedToLibrary": "{0} telah ditambah ke media library anda", "ValueSpecialEpisodeName": "Khas - {0}", "VersionNumber": "Versi {0}", "TaskCleanActivityLog": "Log Aktiviti Bersih", From 838adaea48cf9e1434524b509c03dc3cdff0520e Mon Sep 17 00:00:00 2001 From: Mehyar Date: Sun, 5 Dec 2021 16:44:04 +0000 Subject: [PATCH 074/167] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index a83a453b4c..570d600fc3 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -11,7 +11,7 @@ "Collections": "التجميعات", "DeviceOfflineWithName": "قُطِع الاتصال ب{0}", "DeviceOnlineWithName": "{0} متصل", - "FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}", + "FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فشلت من {0}", "Favorites": "مفضلات", "Folders": "المجلدات", "Genres": "التضنيفات", From bd48b74d5bec29c6163c0d0a09018b6eaadbebfa Mon Sep 17 00:00:00 2001 From: oxixes Date: Sun, 5 Dec 2021 03:50:45 +0000 Subject: [PATCH 075/167] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/ --- Emby.Server.Implementations/Localization/Core/es.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index d3d9d27038..f8c69712e3 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -15,8 +15,8 @@ "Favorites": "Favoritos", "Folders": "Carpetas", "Genres": "Géneros", - "HeaderAlbumArtists": "Artista del álbum", - "HeaderContinueWatching": "Continuar viendo", + "HeaderAlbumArtists": "Artistas del álbum", + "HeaderContinueWatching": "Seguir viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos", From 60f4d70cc5e1326bcead2717da47da98ebf73696 Mon Sep 17 00:00:00 2001 From: Bas Goos Date: Sat, 4 Dec 2021 14:32:55 +0000 Subject: [PATCH 076/167] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- Emby.Server.Implementations/Localization/Core/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 79f921bcb2..9d512dea16 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -15,7 +15,7 @@ "Favorites": "Favorieten", "Folders": "Mappen", "Genres": "Genres", - "HeaderAlbumArtists": "Artiests Album", + "HeaderAlbumArtists": "Album Artiesten", "HeaderContinueWatching": "Kijken hervatten", "HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteArtists": "Favoriete artiesten", From 9d387c542a6b5e7c513de247362df01da3d1fed4 Mon Sep 17 00:00:00 2001 From: mio2 Date: Sun, 5 Dec 2021 16:44:14 +0000 Subject: [PATCH 077/167] Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/ --- Emby.Server.Implementations/Localization/Core/ja.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index c689bc58a5..7f41561ecf 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -11,11 +11,11 @@ "Collections": "コレクション", "DeviceOfflineWithName": "{0} が切断されました", "DeviceOnlineWithName": "{0} が接続されました", - "FailedLoginAttemptWithUserName": "ログインを試行しましたが {0}によって失敗しました", + "FailedLoginAttemptWithUserName": "ログインを試行しましたが {0} によって失敗しました", "Favorites": "お気に入り", "Folders": "フォルダー", "Genres": "ジャンル", - "HeaderAlbumArtists": "アーティストのアルバム", + "HeaderAlbumArtists": "アルバムアーティスト", "HeaderContinueWatching": "視聴を続ける", "HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteArtists": "お気に入りのアーティスト", From 2d77c729f2c2de42c428d3b7c2564ed721a862ea Mon Sep 17 00:00:00 2001 From: mikixd586 Date: Sat, 4 Dec 2021 07:52:17 +0000 Subject: [PATCH 078/167] Translated using Weblate (Serbian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sr/ --- Emby.Server.Implementations/Localization/Core/sr.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json index 2d6f3d53da..e31208e807 100644 --- a/Emby.Server.Implementations/Localization/Core/sr.json +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -64,7 +64,7 @@ "ItemRemovedWithName": "{0} уклоњено из библиотеке", "ItemAddedWithName": "{0} додато у библиотеку", "Inherit": "Наследи", - "HomeVideos": "Кућни видео", + "HomeVideos": "Кућни Видео", "HeaderRecordingGroups": "Групе снимања", "HeaderNextUp": "Следи", "HeaderLiveTV": "ТВ уживо", @@ -117,5 +117,6 @@ "TaskCleanActivityLog": "Очисти историју активности", "Undefined": "Недефинисано", "Forced": "Принудно", - "Default": "Подразумевано" + "Default": "Подразумевано", + "TaskOptimizeDatabase": "Оптимизуј датабазу" } From 5eff80fb8cb36ad6a7e22a090bcf6cc0a2ceeaea Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 4 Dec 2021 18:56:50 +0000 Subject: [PATCH 079/167] Translated using Weblate (Macedonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/ --- Emby.Server.Implementations/Localization/Core/mk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json index 6baedcb2dd..d7839be57a 100644 --- a/Emby.Server.Implementations/Localization/Core/mk.json +++ b/Emby.Server.Implementations/Localization/Core/mk.json @@ -5,7 +5,7 @@ "PluginUninstalledWithName": "{0} беше успешно деинсталирано", "PluginInstalledWithName": "{0} беше успешно инсталирано", "Plugin": "Додатоци", - "Playlists": "Листи", + "Playlists": "Плејлисти", "Photos": "Слики", "NotificationOptionVideoPlaybackStopped": "Видео стопирано", "NotificationOptionVideoPlayback": "Видео пуштено", From 9b71bf8cfec4d7685057f4c5035edc72f8a8eb34 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 4 Dec 2021 18:54:17 +0000 Subject: [PATCH 080/167] Translated using Weblate (Nepali) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ne/ --- Emby.Server.Implementations/Localization/Core/ne.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ne.json b/Emby.Server.Implementations/Localization/Core/ne.json index 8e820d40c7..8584fc0653 100644 --- a/Emby.Server.Implementations/Localization/Core/ne.json +++ b/Emby.Server.Implementations/Localization/Core/ne.json @@ -69,7 +69,7 @@ "UserDeletedWithName": "प्रयोगकर्ता {0} हटाइएको छ", "UserCreatedWithName": "प्रयोगकर्ता {0} सिर्जना गरिएको छ", "User": "प्रयोगकर्ता", - "PluginInstalledWithName": "", + "PluginInstalledWithName": "{0} सभएको थियो", "StartupEmbyServerIsLoading": "Jellyfin सर्भर लोड हुँदैछ। कृपया छिट्टै फेरि प्रयास गर्नुहोस्।", "Songs": "गीतहरू", "Shows": "शोहरू", From b9b39592237de51be64dec11271f93268c3a45e2 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Fri, 3 Dec 2021 09:36:27 +0000 Subject: [PATCH 081/167] Translated using Weblate (Punjabi) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pa/ --- .../Localization/Core/pa.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/pa.json b/Emby.Server.Implementations/Localization/Core/pa.json index d1db09232e..4ac57b630d 100644 --- a/Emby.Server.Implementations/Localization/Core/pa.json +++ b/Emby.Server.Implementations/Localization/Core/pa.json @@ -24,7 +24,7 @@ "TasksLibraryCategory": "ਲਾਇਬ੍ਰੇਰੀ", "TasksMaintenanceCategory": "ਰੱਖ-ਰਖਾਅ", "VersionNumber": "ਵਰਜਨ {0}", - "ValueSpecialEpisodeName": "ਵਿਸ਼ੇਸ਼ - {0}", + "ValueSpecialEpisodeName": "ਖਾਸ - {0}", "ValueHasBeenAddedToLibrary": "{0} ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ", "UserStoppedPlayingItemWithValues": "{0} ਨੇ {2} 'ਤੇ {1} ਖੇਡਣਾ ਪੂਰਾ ਕਰ ਲਿਆ ਹੈ", "UserStartedPlayingItemWithValues": "{0} {2} 'ਤੇ {1} ਖੇਡ ਰਿਹਾ ਹੈ", @@ -43,8 +43,8 @@ "Sync": "ਸਿੰਕ", "SubtitleDownloadFailureFromForItem": "ਉਪਸਿਰਲੇਖ {1} ਲਈ {0} ਤੋਂ ਡਾ toਨਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ", "StartupEmbyServerIsLoading": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ. ਕਿਰਪਾ ਕਰਕੇ ਜਲਦੀ ਹੀ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ.", - "Songs": "ਗਾਣੇ", - "Shows": "ਸ਼ੋਅਜ਼", + "Songs": "ਗਾਣੇਂ", + "Shows": "ਸ਼ੋਅ", "ServerNameNeedsToBeRestarted": "{0} ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ", "ScheduledTaskStartedWithName": "{0} ਸ਼ੁਰੂ ਹੋਇਆ", "ScheduledTaskFailedWithName": "{0} ਅਸਫਲ", @@ -53,7 +53,7 @@ "PluginUninstalledWithName": "{0} ਅਣਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ ਸੀ", "PluginInstalledWithName": "{0} ਲਗਾਇਆ ਗਿਆ ਸੀ", "Plugin": "ਪਲੱਗਇਨ", - "Playlists": "ਪਲੇਲਿਸਟਸ", + "Playlists": "ਪਲੇਸੂਚੀਆਂ", "Photos": "ਫੋਟੋਆਂ", "NotificationOptionVideoPlaybackStopped": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਰੋਕਿਆ ਗਿਆ", "NotificationOptionVideoPlayback": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ", @@ -102,13 +102,13 @@ "HeaderAlbumArtists": "ਐਲਬਮ ਕਲਾਕਾਰ", "Genres": "ਸ਼ੈਲੀਆਂ", "Forced": "ਮਜਬੂਰ", - "Folders": "ਫੋਲਡਰ", + "Folders": "ਫੋਲਡਰਸ", "Favorites": "ਮਨਪਸੰਦ", "FailedLoginAttemptWithUserName": "ਤੋਂ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ {0}", "DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ", "DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ", - "Default": "ਮੂਲ", - "Collections": "ਸੰਗ੍ਰਹਿ", + "Default": "ਡਿਫੌਲਟ", + "Collections": "ਸੰਗ੍ਰਹਿਣ", "ChapterNameValue": "ਅਧਿਆਇ {0}", "Channels": "ਚੈਨਲ", "CameraImageUploadedFrom": "ਤੋਂ ਇੱਕ ਨਵਾਂ ਕੈਮਰਾ ਚਿੱਤਰ ਅਪਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ {0}", From 29095ab390b3ed92224510679d7d26b71e978f29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 12:01:19 +0000 Subject: [PATCH 082/167] Bump BDInfo from 0.7.6.1 to 0.7.6.2 Bumps [BDInfo](https://github.com/jellyfin/BDInfo) from 0.7.6.1 to 0.7.6.2. - [Release notes](https://github.com/jellyfin/BDInfo/releases) - [Commits](https://github.com/jellyfin/BDInfo/commits) --- updated-dependencies: - dependency-name: BDInfo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 6bb8bcdab3..4b65dab709 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -22,7 +22,7 @@ - + From c4c7d7431fc96d769d30e46f921d0b151f619afc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 13:29:10 +0000 Subject: [PATCH 083/167] Bump libse from 3.6.2 to 3.6.4 Bumps [libse](https://github.com/SubtitleEdit/subtitleedit) from 3.6.2 to 3.6.4. - [Release notes](https://github.com/SubtitleEdit/subtitleedit/releases) - [Changelog](https://github.com/SubtitleEdit/subtitleedit/blob/master/Changelog.txt) - [Commits](https://github.com/SubtitleEdit/subtitleedit/compare/3.6.2...3.6.4) --- updated-dependencies: - dependency-name: libse dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 4b65dab709..9f6d8e7fe9 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -23,7 +23,7 @@ - + From 3176a4ddd956a16f95b14ccedf2f6aa344019ab9 Mon Sep 17 00:00:00 2001 From: matthiasdv Date: Mon, 6 Dec 2021 22:40:00 +0100 Subject: [PATCH 084/167] add more hardening to systemd service --- debian/jellyfin.service | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/debian/jellyfin.service b/debian/jellyfin.service index e215a85362..071f949dd9 100644 --- a/debian/jellyfin.service +++ b/debian/jellyfin.service @@ -13,7 +13,20 @@ TimeoutSec = 15 NoNewPrivileges=true SystemCallArchitectures=native RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK -ProtectKernelModules=True +RestrictNamespaces=true +RestrictRealtime=true +RestrictSUIDSGID=true +ProtectClock=true +ProtectControlGroups=true +ProtectHostname=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +LockPersonality=true +PrivateTmp=true +PrivateDevices=false +PrivateUsers=true +RemoveIPC=true SystemCallFilter=~@clock SystemCallFilter=~@aio SystemCallFilter=~@chown From fde84a1e00b0c781ce10acc73a9103db51aab67b Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 7 Dec 2021 15:18:17 +0100 Subject: [PATCH 085/167] Refactor extras parsing --- .../AudioBook/AudioBookListResolver.cs | 10 +- Emby.Naming/Common/NamingOptions.cs | 12 +- Emby.Naming/Video/ExtraResolver.cs | 103 +++++--- Emby.Naming/Video/FileStack.cs | 5 + Emby.Naming/Video/StackResolver.cs | 77 +++--- Emby.Naming/Video/VideoInfo.cs | 13 +- Emby.Naming/Video/VideoListResolver.cs | 169 +++---------- Emby.Naming/Video/VideoResolver.cs | 7 +- .../Library/LibraryManager.cs | 126 ++++------ .../Library/Resolvers/BaseVideoResolver.cs | 131 +++------- .../Library/Resolvers/Movies/MovieResolver.cs | 103 +++++--- .../Library/Resolvers/TV/EpisodeResolver.cs | 44 ++-- .../Controllers/UserLibraryController.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 235 ++---------------- .../Entities/IHasTrailers.cs | 68 +---- .../Entities/Movies/BoxSet.cs | 12 +- .../Entities/Movies/Movie.cs | 82 ++---- .../Entities/TV/Episode.cs | 14 +- MediaBrowser.Controller/Entities/TV/Series.cs | 10 +- .../Entities/UserViewBuilder.cs | 9 +- .../Library/ILibraryManager.cs | 17 +- .../Images/LocalImageProvider.cs | 23 +- .../Jellyfin.Naming.Tests/Video/ExtraTests.cs | 11 +- .../Video/MultiVersionTests.cs | 20 +- .../Jellyfin.Naming.Tests/Video/StackTests.cs | 93 ++----- .../Video/VideoListResolverTests.cs | 107 +++++--- .../Library/EpisodeResolverTest.cs | 13 +- .../Library/LibraryManager/FindExtrasTests.cs | 178 +++++++++++++ 28 files changed, 689 insertions(+), 1005 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 1e4a8d2edc..dd8a05bb3e 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -14,6 +14,7 @@ namespace Emby.Naming.AudioBook public class AudioBookListResolver { private readonly NamingOptions _options; + private readonly AudioBookResolver _audioBookResolver; /// /// Initializes a new instance of the class. @@ -22,6 +23,7 @@ namespace Emby.Naming.AudioBook public AudioBookListResolver(NamingOptions options) { _options = options; + _audioBookResolver = new AudioBookResolver(_options); } /// @@ -31,21 +33,19 @@ namespace Emby.Naming.AudioBook /// Returns IEnumerable of . public IEnumerable Resolve(IEnumerable files) { - var audioBookResolver = new AudioBookResolver(_options); // File with empty fullname will be sorted out here. var audiobookFileInfos = files - .Select(i => audioBookResolver.Resolve(i.FullName)) + .Select(i => _audioBookResolver.Resolve(i.FullName)) .OfType() .ToList(); - var stackResult = new StackResolver(_options) - .ResolveAudioBooks(audiobookFileInfos); + var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos); foreach (var stack in stackResult) { var stackFiles = stack.Files - .Select(i => audioBookResolver.Resolve(i)) + .Select(i => _audioBookResolver.Resolve(i)) .OfType() .ToList(); diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 7bc9fbce84..86c79166d1 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -126,9 +126,9 @@ namespace Emby.Naming.Common VideoFileStackingExpressions = new[] { - "(?.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$", - "(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$", - "(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$" + "^(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|part|pt|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$", + "^(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|part|pt|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$", + "^(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$" }; CleanDateTimes = new[] @@ -403,6 +403,12 @@ namespace Emby.Naming.Common VideoExtraRules = new[] { + new ExtraRule( + ExtraType.Trailer, + ExtraRuleType.DirectoryName, + "trailers", + MediaType.Video), + new ExtraRule( ExtraType.Trailer, ExtraRuleType.Filename, diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index 7bc226614e..cd7a6c0d7a 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Audio; using Emby.Naming.Common; @@ -9,45 +11,27 @@ namespace Emby.Naming.Video /// <summary> /// Resolve if file is extra for video. /// </summary> - public class ExtraResolver + public static class ExtraResolver { - private static readonly char[] _digits = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; - private readonly NamingOptions _options; - - /// <summary> - /// Initializes a new instance of the <see cref="ExtraResolver"/> class. - /// </summary> - /// <param name="options"><see cref="NamingOptions"/> object containing VideoExtraRules and passed to <see cref="AudioFileParser"/> and <see cref="VideoResolver"/>.</param> - public ExtraResolver(NamingOptions options) - { - _options = options; - } + private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; /// <summary> /// Attempts to resolve if file is extra. /// </summary> /// <param name="path">Path to file.</param> + /// <param name="namingOptions">The naming options.</param> /// <returns>Returns <see cref="ExtraResult"/> object.</returns> - public ExtraResult GetExtraInfo(string path) + public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions) { var result = new ExtraResult(); - for (var i = 0; i < _options.VideoExtraRules.Length; i++) + for (var i = 0; i < namingOptions.VideoExtraRules.Length; i++) { - var rule = _options.VideoExtraRules[i]; - if (rule.MediaType == MediaType.Audio) + var rule = namingOptions.VideoExtraRules[i]; + if ((rule.MediaType == MediaType.Audio && !AudioFileParser.IsAudioFile(path, namingOptions)) + || (rule.MediaType == MediaType.Video && !VideoResolver.IsVideoFile(path, namingOptions))) { - if (!AudioFileParser.IsAudioFile(path, _options)) - { - continue; - } - } - else if (rule.MediaType == MediaType.Video) - { - if (!VideoResolver.IsVideoFile(path, _options)) - { - continue; - } + continue; } var pathSpan = path.AsSpan(); @@ -76,7 +60,7 @@ namespace Emby.Naming.Video { var filename = Path.GetFileName(path); - var regex = new Regex(rule.Token, RegexOptions.IgnoreCase); + var regex = new Regex(rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled); if (regex.IsMatch(filename)) { @@ -102,5 +86,68 @@ namespace Emby.Naming.Video return result; } + + /// <summary> + /// Finds extras matching the video info. + /// </summary> + /// <param name="files">The list of file video infos.</param> + /// <param name="videoInfo">The video to compare against.</param> + /// <param name="videoFlagDelimiters">The video flag delimiters.</param> + /// <returns>A list of video extras for [videoInfo].</returns> + public static IReadOnlyList<VideoFileInfo> GetExtras(IReadOnlyList<VideoInfo> files, VideoFileInfo videoInfo, ReadOnlySpan<char> videoFlagDelimiters) + { + var parentDir = videoInfo.IsDirectory ? videoInfo.Path : Path.GetDirectoryName(videoInfo.Path.AsSpan()); + + var trimmedFileName = TrimFilenameDelimiters(videoInfo.Name, videoFlagDelimiters); + var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(videoInfo.FileNameWithoutExtension, videoFlagDelimiters); + var trimmedVideoInfoName = TrimFilenameDelimiters(videoInfo.Name, videoFlagDelimiters); + + var result = new List<VideoFileInfo>(); + for (var pos = files.Count - 1; pos >= 0; pos--) + { + var current = files[pos]; + // ignore non-extras and multi-file (can this happen?) + if (current.ExtraType == null || current.Files.Count > 1) + { + continue; + } + + var currentFile = files[pos].Files[0]; + var trimmedCurrentFileName = TrimFilenameDelimiters(currentFile.Name, videoFlagDelimiters); + + // first check filenames + bool isValid = StartsWith(trimmedCurrentFileName, trimmedFileNameWithoutExtension) + || (StartsWith(trimmedCurrentFileName, trimmedFileName) && currentFile.Year == videoInfo.Year) + || (StartsWith(trimmedCurrentFileName, trimmedVideoInfoName) && currentFile.Year == videoInfo.Year); + + // then by directory + if (!isValid) + { + // When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name + var currentParentDir = currentFile.ExtraRule?.RuleType == ExtraRuleType.DirectoryName + ? Path.GetDirectoryName(Path.GetDirectoryName(currentFile.Path.AsSpan())) + : Path.GetDirectoryName(currentFile.Path.AsSpan()); + + isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase); + } + + if (isValid) + { + result.Add(currentFile); + } + } + + return result.OrderBy(r => r.Path).ToArray(); + } + + private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters) + { + return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd(); + } + + private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName) + { + return !baseName.IsEmpty && fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase); + } } } diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs index 6519db57c3..a4a4716ca9 100644 --- a/Emby.Naming/Video/FileStack.cs +++ b/Emby.Naming/Video/FileStack.cs @@ -40,6 +40,11 @@ namespace Emby.Naming.Video /// <returns>True if file is in the stack.</returns> public bool ContainsFile(string file, bool isDirectory) { + if (string.IsNullOrEmpty(file)) + { + return false; + } + if (IsDirectoryStack == isDirectory) { return Files.Contains(file, StringComparer.OrdinalIgnoreCase); diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index 36f65a5624..be73f69db1 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -12,37 +12,28 @@ namespace Emby.Naming.Video /// <summary> /// Resolve <see cref="FileStack"/> from list of paths. /// </summary> - public class StackResolver + public static class StackResolver { - private readonly NamingOptions _options; - - /// <summary> - /// Initializes a new instance of the <see cref="StackResolver"/> class. - /// </summary> - /// <param name="options"><see cref="NamingOptions"/> object containing VideoFileStackingRegexes and passes options to <see cref="VideoResolver"/>.</param> - public StackResolver(NamingOptions options) - { - _options = options; - } - /// <summary> /// Resolves only directories from paths. /// </summary> /// <param name="files">List of paths.</param> + /// <param name="namingOptions">The naming options.</param> /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> - public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files) + public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions) { - return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true })); + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions); } /// <summary> /// Resolves only files from paths. /// </summary> /// <param name="files">List of paths.</param> + /// <param name="namingOptions">The naming options.</param> /// <returns>Enumerable <see cref="FileStack"/> of files.</returns> - public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files) + public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions) { - return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false })); + return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions); } /// <summary> @@ -50,7 +41,7 @@ namespace Emby.Naming.Video /// </summary> /// <param name="files">List of paths.</param> /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> - public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files) + public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files) { var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path)); @@ -82,15 +73,20 @@ namespace Emby.Naming.Video /// Resolves videos from paths. /// </summary> /// <param name="files">List of paths.</param> + /// <param name="namingOptions">The naming options.</param> /// <returns>Enumerable <see cref="FileStack"/> of videos.</returns> - public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files) + public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions) { var list = files - .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options)) + .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions)) .OrderBy(i => i.FullName) + .Select(f => (f.IsDirectory, FileName: GetFileNameWithExtension(f), f.FullName)) .ToList(); - var expressions = _options.VideoFileStackingRegexes; + // TODO is there a "nicer" way? + var cache = new Dictionary<(string, Regex, int), Match>(); + + var expressions = namingOptions.VideoFileStackingRegexes; for (var i = 0; i < list.Count; i++) { @@ -102,17 +98,17 @@ namespace Emby.Naming.Video while (expressionIndex < expressions.Length) { var exp = expressions[expressionIndex]; - var stack = new FileStack(); + FileStack? stack = null; // (Title)(Volume)(Ignore)(Extension) - var match1 = FindMatch(file1, exp, offset); + var match1 = FindMatch(file1.FileName, exp, offset, cache); if (match1.Success) { - var title1 = match1.Groups["title"].Value; - var volume1 = match1.Groups["volume"].Value; - var ignore1 = match1.Groups["ignore"].Value; - var extension1 = match1.Groups["extension"].Value; + var title1 = match1.Groups[1].Value; + var volume1 = match1.Groups[2].Value; + var ignore1 = match1.Groups[3].Value; + var extension1 = match1.Groups[4].Value; var j = i + 1; while (j < list.Count) @@ -126,7 +122,7 @@ namespace Emby.Naming.Video } // (Title)(Volume)(Ignore)(Extension) - var match2 = FindMatch(file2, exp, offset); + var match2 = FindMatch(file2.FileName, exp, offset, cache); if (match2.Success) { @@ -142,6 +138,7 @@ namespace Emby.Naming.Video if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase) && string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase)) { + stack ??= new FileStack(); if (stack.Files.Count == 0) { stack.Name = title1 + ignore1; @@ -204,7 +201,7 @@ namespace Emby.Naming.Video expressionIndex++; } - if (stack.Files.Count > 1) + if (stack?.Files.Count > 1) { yield return stack; i += stack.Files.Count - 1; @@ -214,26 +211,32 @@ namespace Emby.Naming.Video } } - private static string GetRegexInput(FileSystemMetadata file) + private static string GetFileNameWithExtension(FileSystemMetadata file) { // For directories, dummy up an extension otherwise the expressions will fail - var input = !file.IsDirectory - ? file.FullName - : file.FullName + ".mkv"; + var input = file.FullName; + if (file.IsDirectory) + { + input = Path.ChangeExtension(input, "mkv"); + } return Path.GetFileName(input); } - private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset) + private static Match FindMatch(string input, Regex regex, int offset, Dictionary<(string, Regex, int), Match> cache) { - var regexInput = GetRegexInput(input); - - if (offset < 0 || offset >= regexInput.Length) + if (offset < 0 || offset >= input.Length) { return Match.Empty; } - return regex.Match(regexInput, offset); + if (!cache.TryGetValue((input, regex, offset), out var result)) + { + result = regex.Match(input, offset, input.Length - offset); + cache.Add((input, regex, offset), result); + } + + return result; } } } diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs index 930fdb33f8..8847ee9bc9 100644 --- a/Emby.Naming/Video/VideoInfo.cs +++ b/Emby.Naming/Video/VideoInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using MediaBrowser.Model.Entities; namespace Emby.Naming.Video { @@ -17,7 +18,6 @@ namespace Emby.Naming.Video Name = name; Files = Array.Empty<VideoFileInfo>(); - Extras = Array.Empty<VideoFileInfo>(); AlternateVersions = Array.Empty<VideoFileInfo>(); } @@ -39,16 +39,15 @@ namespace Emby.Naming.Video /// <value>The files.</value> public IReadOnlyList<VideoFileInfo> Files { get; set; } - /// <summary> - /// Gets or sets the extras. - /// </summary> - /// <value>The extras.</value> - public IReadOnlyList<VideoFileInfo> Extras { get; set; } - /// <summary> /// Gets or sets the alternate versions. /// </summary> /// <value>The alternate versions.</value> public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; } + + /// <summary> + /// Gets or sets the extra type. + /// </summary> + public ExtraType? ExtraType { get; set; } } } diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index ed7d511a39..bce7cb47f1 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Common; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; namespace Emby.Naming.Video @@ -20,11 +19,12 @@ namespace Emby.Naming.Video /// <param name="files">List of related video files.</param> /// <param name="namingOptions">The naming options.</param> /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> + /// <param name="parseName">Whether to parse the name or use the filename.</param> /// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns> - public static IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true) + public static IReadOnlyList<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true) { var videoInfos = files - .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions)) + .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions, parseName)) .OfType<VideoFileInfo>() .ToList(); @@ -34,12 +34,25 @@ namespace Emby.Naming.Video .Where(i => i.ExtraType == null) .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); - var stackResult = new StackResolver(namingOptions) - .Resolve(nonExtras).ToList(); + var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList(); - var remainingFiles = videoInfos - .Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory))) - .ToList(); + var remainingFiles = new List<VideoFileInfo>(); + var standaloneMedia = new List<VideoFileInfo>(); + + for (var i = 0; i < videoInfos.Count; i++) + { + var current = videoInfos[i]; + if (stackResult.Any(s => s.ContainsFile(current.Path, current.IsDirectory))) + { + continue; + } + + remainingFiles.Add(current); + if (current.ExtraType == null) + { + standaloneMedia.Add(current); + } + } var list = new List<VideoInfo>(); @@ -47,27 +60,15 @@ namespace Emby.Naming.Video { var info = new VideoInfo(stack.Name) { - Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions)) + Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName)) .OfType<VideoFileInfo>() .ToList() }; info.Year = info.Files[0].Year; - - var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters); - - if (extras.Count > 0) - { - info.Extras = extras; - } - list.Add(info); } - var standaloneMedia = remainingFiles - .Where(i => i.ExtraType == null) - .ToList(); - foreach (var media in standaloneMedia) { var info = new VideoInfo(media.Name) { Files = new[] { media } }; @@ -75,10 +76,6 @@ namespace Emby.Naming.Video info.Year = info.Files[0].Year; remainingFiles.Remove(media); - var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters); - - info.Extras = extras; - list.Add(info); } @@ -87,58 +84,12 @@ namespace Emby.Naming.Video list = GetVideosGroupedByVersion(list, namingOptions); } - // If there's only one resolved video, use the folder name as well to find extras - if (list.Count == 1) - { - var info = list[0]; - var videoPath = list[0].Files[0].Path; - var parentPath = Path.GetDirectoryName(videoPath.AsSpan()); - - if (!parentPath.IsEmpty) - { - var folderName = Path.GetFileName(parentPath); - if (!folderName.IsEmpty) - { - var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters); - extras.AddRange(info.Extras); - info.Extras = extras; - } - } - - // Add the extras that are just based on file name as well - var extrasByFileName = remainingFiles - .Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename) - .ToList(); - - remainingFiles = remainingFiles - .Except(extrasByFileName) - .ToList(); - - extrasByFileName.AddRange(info.Extras); - info.Extras = extrasByFileName; - } - - // If there's only one video, accept all trailers - // Be lenient because people use all kinds of mishmash conventions with trailers. - if (list.Count == 1) - { - var trailers = remainingFiles - .Where(i => i.ExtraType == ExtraType.Trailer) - .ToList(); - - trailers.AddRange(list[0].Extras); - list[0].Extras = trailers; - - remainingFiles = remainingFiles - .Except(trailers) - .ToList(); - } - // Whatever files are left, just add them list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name) { Files = new[] { i }, - Year = i.Year + Year = i.Year, + ExtraType = i.ExtraType })); return list; @@ -162,6 +113,11 @@ namespace Emby.Naming.Video for (var i = 0; i < videos.Count; i++) { var video = videos[i]; + if (video.ExtraType != null) + { + continue; + } + if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions)) { return videos; @@ -178,17 +134,14 @@ namespace Emby.Naming.Video var alternateVersionsLen = videos.Count - 1; var alternateVersions = new VideoFileInfo[alternateVersionsLen]; - var extras = new List<VideoFileInfo>(list[0].Extras); for (int i = 0; i < alternateVersionsLen; i++) { var video = videos[i + 1]; alternateVersions[i] = video.Files[0]; - extras.AddRange(video.Extras); } list[0].AlternateVersions = alternateVersions; list[0].Name = folderName.ToString(); - list[0].Extras = extras; return list; } @@ -230,7 +183,7 @@ namespace Emby.Naming.Video var tmpTestFilename = testFilename.ToString(); if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName)) { - tmpTestFilename = cleanName.Trim().ToString(); + tmpTestFilename = cleanName.Trim(); } // The CleanStringParser should have removed common keywords etc. @@ -238,67 +191,5 @@ namespace Emby.Naming.Video || testFilename[0] == '-' || Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled); } - - private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters) - { - return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd(); - } - - private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName, ReadOnlySpan<char> trimmedBaseName) - { - if (baseName.IsEmpty) - { - return false; - } - - return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase) - || (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase)); - } - - /// <summary> - /// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles]. - /// </summary> - /// <param name="remainingFiles">The list of remaining filenames.</param> - /// <param name="baseName">The base name to use for the comparison.</param> - /// <param name="videoFlagDelimiters">The video flag delimiters.</param> - /// <returns>A list of video extras for [baseName].</returns> - private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> baseName, ReadOnlySpan<char> videoFlagDelimiters) - { - return ExtractExtras(remainingFiles, baseName, ReadOnlySpan<char>.Empty, videoFlagDelimiters); - } - - /// <summary> - /// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles]. - /// </summary> - /// <param name="remainingFiles">The list of remaining filenames.</param> - /// <param name="firstBaseName">The first base name to use for the comparison.</param> - /// <param name="secondBaseName">The second base name to use for the comparison.</param> - /// <param name="videoFlagDelimiters">The video flag delimiters.</param> - /// <returns>A list of video extras for [firstBaseName] and [secondBaseName].</returns> - private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> firstBaseName, ReadOnlySpan<char> secondBaseName, ReadOnlySpan<char> videoFlagDelimiters) - { - var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters); - var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters); - - var result = new List<VideoFileInfo>(); - for (var pos = remainingFiles.Count - 1; pos >= 0; pos--) - { - var file = remainingFiles[pos]; - if (file.ExtraType == null) - { - continue; - } - - var filename = file.FileNameWithoutExtension; - if (StartsWith(filename, firstBaseName, trimmedFirstBaseName) - || StartsWith(filename, secondBaseName, trimmedSecondBaseName)) - { - result.Add(file); - remainingFiles.RemoveAt(pos); - } - } - - return result; - } } } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 4c9df27f50..9cadc14658 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -16,10 +16,11 @@ namespace Emby.Naming.Video /// </summary> /// <param name="path">The path.</param> /// <param name="namingOptions">The naming options.</param> + /// <param name="parseName">Whether to parse the name or use the filename.</param> /// <returns>VideoFileInfo.</returns> - public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions) + public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true) { - return Resolve(path, true, namingOptions); + return Resolve(path, true, namingOptions, parseName); } /// <summary> @@ -74,7 +75,7 @@ namespace Emby.Naming.Video var format3DResult = Format3DParser.Parse(path, namingOptions); - var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path); + var extraResult = ExtraResolver.GetExtraInfo(path, namingOptions); var name = Path.GetFileNameWithoutExtension(path); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 778b6225e1..01749242fa 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -11,11 +11,9 @@ using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Emby.Naming.Audio; using Emby.Naming.Common; using Emby.Naming.TV; using Emby.Naming.Video; -using Emby.Server.Implementations.Library.Resolvers; using Emby.Server.Implementations.Library.Validators; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks; @@ -677,7 +675,7 @@ namespace Emby.Server.Implementations.Library { var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService); - if (result != null && result.Items.Count > 0) + if (result?.Items.Count > 0) { var items = new List<BaseItem>(); items.AddRange(result.Items); @@ -2685,89 +2683,58 @@ namespace Emby.Server.Implementations.Library }; } - public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) + public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren) { - var namingOptions = _namingOptions; - - var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory) - .Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)) - .SelectMany(i => _fileSystem.GetFiles(i.FullName, namingOptions.VideoFileExtensions, false, false)) - .ToList(); - - var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions); - - var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase)); - - if (currentVideo != null) + var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions); + if (ownerVideoInfo == null) { - files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path))); + yield break; } - var resolvers = new IItemResolver[] + var count = fileSystemChildren.Count; + var files = new List<FileSystemMetadata>(); + for (var i = 0; i < count; i++) { - new GenericVideoResolver<Trailer>(_namingOptions) - }; - - return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers) - .OfType<Trailer>() - .Select(video => + var current = fileSystemChildren[i]; + if (current.IsDirectory && BaseItem.AllExtrasTypesFolderNames.ContainsKey(current.Name)) { - // Try to retrieve it from the db. If we don't find it, use the resolved version - if (GetItemById(video.Id) is Trailer dbItem) - { - video = dbItem; - } - - video.ParentId = Guid.Empty; - video.OwnerId = owner.Id; - video.ExtraType = ExtraType.Trailer; - video.TrailerTypes = new[] { TrailerType.LocalTrailer }; - - return video; - - // Sort them so that the list can be easily compared for changes - }).OrderBy(i => i.Path); - } - - public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) - { - var namingOptions = _namingOptions; - - var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory) - .Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty)) - .SelectMany(i => _fileSystem.GetFiles(i.FullName, namingOptions.VideoFileExtensions, false, false)) - .ToList(); - - var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions); - - var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase)); - - if (currentVideo != null) - { - files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path))); + files.AddRange(_fileSystem.GetFiles(current.FullName, _namingOptions.VideoFileExtensions, false, false)); + } + else if (!current.IsDirectory) + { + files.Add(current); + } } - return ResolvePaths(files, directoryService, null, new LibraryOptions(), null) - .OfType<Video>() - .Select(video => + if (files.Count == 0) + { + yield break; + } + + var videos = VideoListResolver.Resolve(files, _namingOptions); + // owner video info cannot be null as that implies it has no path + var extras = ExtraResolver.GetExtras(videos, ownerVideoInfo, _namingOptions.VideoFlagDelimiters); + + for (var i = 0; i < extras.Count; i++) + { + var currentExtra = extras[i]; + var resolved = ResolvePath(_fileSystem.GetFileInfo(currentExtra.Path)); + if (resolved is not Video video) { - // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = GetItemById(video.Id) as Video; + continue; + } - if (dbItem != null) - { - video = dbItem; - } + // Try to retrieve it from the db. If we don't find it, use the resolved version + if (GetItemById(resolved.Id) is Video dbItem) + { + video = dbItem; + } - video.ParentId = Guid.Empty; - video.OwnerId = owner.Id; - - SetExtraTypeFromFilename(video); - - return video; - - // Sort them so that the list can be easily compared for changes - }).OrderBy(i => i.Path); + video.ExtraType = currentExtra.ExtraType; + video.ParentId = Guid.Empty; + video.OwnerId = owner.Id; + yield return video; + } } public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem) @@ -2817,15 +2784,6 @@ namespace Emby.Server.Implementations.Library return path; } - private void SetExtraTypeFromFilename(Video item) - { - var resolver = new ExtraResolver(_namingOptions); - - var result = resolver.GetExtraInfo(item.Path); - - item.ExtraType = result.ExtraType; - } - public List<PersonInfo> GetPeople(InternalPeopleQuery query) { return _itemRepository.GetPeople(query); diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 0ebf0e5302..9222a94797 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -49,120 +49,71 @@ namespace Emby.Server.Implementations.Library.Resolvers protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName) where TVideoType : Video, new() { - var namingOptions = NamingOptions; + VideoFileInfo videoInfo = null; + VideoType? videoType = null; // If the path is a file check for a matching extensions if (args.IsDirectory) { - TVideoType video = null; - VideoFileInfo videoInfo = null; - // Loop through each child file/folder and see if we find a video foreach (var child in args.FileSystemChildren) { var filename = child.Name; - if (child.IsDirectory) { if (IsDvdDirectory(child.FullName, filename, args.DirectoryService)) { - videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); - - if (videoInfo == null) - { - return null; - } - - video = new TVideoType - { - Path = args.Path, - VideoType = VideoType.Dvd, - ProductionYear = videoInfo.Year - }; - break; + videoType = VideoType.Dvd; } - - if (IsBluRayDirectory(filename)) + else if (IsBluRayDirectory(filename)) { - videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); - - if (videoInfo == null) - { - return null; - } - - video = new TVideoType - { - Path = args.Path, - VideoType = VideoType.BluRay, - ProductionYear = videoInfo.Year - }; - break; + videoType = VideoType.BluRay; } } else if (IsDvdFile(filename)) { - videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); - - if (videoInfo == null) - { - return null; - } - - video = new TVideoType - { - Path = args.Path, - VideoType = VideoType.Dvd, - ProductionYear = videoInfo.Year - }; - break; + videoType = VideoType.Dvd; } + + if (videoType == null) + { + continue; + } + + videoInfo = VideoResolver.ResolveDirectory(args.Path, NamingOptions, parseName); + break; } - - if (video != null) - { - video.Name = parseName ? - videoInfo.Name : - Path.GetFileName(args.Path); - - Set3DFormat(video, videoInfo); - } - - return video; } else { - var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false); - - if (videoInfo == null) - { - return null; - } - - if (VideoResolver.IsVideoFile(args.Path, NamingOptions) || videoInfo.IsStub) - { - var path = args.Path; - - var video = new TVideoType - { - Path = path, - IsInMixedFolder = true, - ProductionYear = videoInfo.Year - }; - - SetVideoType(video, videoInfo); - - video.Name = parseName ? - videoInfo.Name : - Path.GetFileNameWithoutExtension(args.Path); - - Set3DFormat(video, videoInfo); - - return video; - } + videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName); } - return null; + if (videoInfo == null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions))) + { + return null; + } + + var video = new TVideoType + { + Name = videoInfo.Name, + Path = args.Path, + ProductionYear = videoInfo.Year, + ExtraType = videoInfo.ExtraType + }; + + if (videoType.HasValue) + { + video.VideoType = videoType.Value; + } + else + { + SetVideoType(video, videoInfo); + } + + Set3DFormat(video, videoInfo); + + return video; } protected void SetVideoType(Video video, VideoFileInfo videoInfo) @@ -207,8 +158,8 @@ namespace Emby.Server.Implementations.Library.Resolvers { // use disc-utils, both DVDs and BDs use UDF filesystem using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read)) + using (UdfReader udfReader = new UdfReader(videoFileStream)) { - UdfReader udfReader = new UdfReader(videoFileStream); if (udfReader.DirectoryExists("VIDEO_TS")) { video.IsoType = IsoType.Dvd; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 732be0fe5c..58279af9ef 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -26,7 +26,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver { private readonly IImageProcessor _imageProcessor; - private readonly StackResolver _stackResolver; private string[] _validCollectionTypes = new[] { @@ -46,7 +45,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies : base(namingOptions) { _imageProcessor = imageProcessor; - _stackResolver = new StackResolver(NamingOptions); } /// <summary> @@ -62,7 +60,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies string collectionType, IDirectoryService directoryService) { - var result = ResolveMultipleInternal(parent, files, collectionType, directoryService); + var result = ResolveMultipleInternal(parent, files, collectionType); if (result != null) { @@ -92,16 +90,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } + Video movie = null; var files = args.GetActualFileSystemChildren().ToList(); if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); + movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); + movie = FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); } if (string.IsNullOrEmpty(collectionType)) @@ -118,17 +117,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - { - return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true); - } + movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true); } if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true); + movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true); } - return null; + // ignore extras + return movie?.ExtraType == null ? movie : null; } // Handle owned items @@ -169,6 +167,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies item = ResolveVideo<Video>(args, false); } + // Ignore extras + if (item?.ExtraType != null) + { + return null; + } + if (item != null) { item.IsInMixedFolder = true; @@ -180,8 +184,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private MultiItemResolverResult ResolveMultipleInternal( Folder parent, List<FileSystemMetadata> files, - string collectionType, - IDirectoryService directoryService) + string collectionType) { if (IsInvalid(parent, collectionType)) { @@ -190,13 +193,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return ResolveVideos<MusicVideo>(parent, files, directoryService, true, collectionType, false); + return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) || string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)) { - return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false); + return ResolveVideos<Video>(parent, files, false, collectionType, false); } if (string.IsNullOrEmpty(collectionType)) @@ -204,7 +207,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies // Owned items should just use the plain video type if (parent == null) { - return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false); + return ResolveVideos<Video>(parent, files, false, collectionType, false); } if (parent is Series || parent.GetParents().OfType<Series>().Any()) @@ -212,12 +215,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - return ResolveVideos<Movie>(parent, files, directoryService, false, collectionType, true); + return ResolveVideos<Movie>(parent, files, false, collectionType, true); } if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) { - return ResolveVideos<Movie>(parent, files, directoryService, true, collectionType, true); + return ResolveVideos<Movie>(parent, files, true, collectionType, true); } return null; @@ -226,21 +229,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private MultiItemResolverResult ResolveVideos<T>( Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, - IDirectoryService directoryService, - bool suppportMultiEditions, + bool supportMultiEditions, string collectionType, bool parseName) where T : Video, new() { var files = new List<FileSystemMetadata>(); - var videos = new List<BaseItem>(); var leftOver = new List<FileSystemMetadata>(); + var hasCollectionType = !string.IsNullOrEmpty(collectionType); // Loop through each child file/folder and see if we find a video foreach (var child in fileSystemEntries) { // This is a hack but currently no better way to resolve a sometimes ambiguous situation - if (string.IsNullOrEmpty(collectionType)) + if (!hasCollectionType) { if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase) || string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase)) @@ -259,29 +261,35 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } } - var resolverResult = VideoListResolver.Resolve(files, NamingOptions, suppportMultiEditions).ToList(); + var resolverResult = VideoListResolver.Resolve(files, NamingOptions, supportMultiEditions, parseName); var result = new MultiItemResolverResult { - ExtraFiles = leftOver, - Items = videos + ExtraFiles = leftOver }; - var isInMixedFolder = resolverResult.Count > 1 || (parent != null && parent.IsTopParent); + var isInMixedFolder = resolverResult.Count > 1 || parent?.IsTopParent == true; foreach (var video in resolverResult) { var firstVideo = video.Files[0]; + var path = firstVideo.Path; + if (video.ExtraType != null) + { + // TODO + result.ExtraFiles.Add(files.First(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnoreCase))); + continue; + } + + var additionalParts = video.Files.Count > 1 ? video.Files.Skip(1).Select(i => i.Path).ToArray() : Array.Empty<string>(); var videoItem = new T { - Path = video.Files[0].Path, + Path = path, IsInMixedFolder = isInMixedFolder, ProductionYear = video.Year, - Name = parseName ? - video.Name : - Path.GetFileNameWithoutExtension(video.Files[0].Path), - AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToArray(), + Name = parseName ? video.Name : firstVideo.Name, + AdditionalParts = additionalParts, LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray() }; @@ -299,21 +307,34 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private static bool IsIgnored(string filename) { // Ignore samples - Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase); + Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); return m.Success; } - private bool ContainsFile(List<VideoInfo> result, FileSystemMetadata file) + private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file) { - return result.Any(i => ContainsFile(i, file)); - } + for (var i = 0; i < result.Count; i++) + { + var current = result[i]; + for (var j = 0; j < current.Files.Count; j++) + { + if (ContainsFile(current.Files[j], file)) + { + return true; + } + } - private bool ContainsFile(VideoInfo result, FileSystemMetadata file) - { - return result.Files.Any(i => ContainsFile(i, file)) || - result.AlternateVersions.Any(i => ContainsFile(i, file)) || - result.Extras.Any(i => ContainsFile(i, file)); + for (var j = 0; j < current.AlternateVersions.Count; j++) + { + if (ContainsFile(current.AlternateVersions[j], file)) + { + return true; + } + } + } + + return false; } private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file) @@ -431,7 +452,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies // TODO: Allow GetMultiDiscMovie in here const bool SupportsMultiVersion = true; - var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ?? + var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ?? new MultiItemResolverResult(); if (result.Items.Count == 1) @@ -510,7 +531,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - var result = _stackResolver.ResolveDirectories(folderPaths).ToList(); + var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList(); if (result.Count != 1) { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index f72da3617b..928cd42ddd 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -45,34 +45,36 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // Also handle flat tv folders - if ((season != null || - string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || - args.HasParent<Series>()) - && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name))) + if (season != null || + string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || + args.HasParent<Series>()) { var episode = ResolveVideo<Episode>(args, false); - if (episode != null) + // Ignore extras + if (episode == null || episode.ExtraType != null) { - var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault(); + return null; + } - if (series != null) - { - episode.SeriesId = series.Id; - episode.SeriesName = series.Name; - } + var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault(); - if (season != null) - { - episode.SeasonId = season.Id; - episode.SeasonName = season.Name; - } + if (series != null) + { + episode.SeriesId = series.Id; + episode.SeriesName = series.Name; + } - // Assume season 1 if there's no season folder and a season number could not be determined - if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue)) - { - episode.ParentIndexNumber = 1; - } + if (season != null) + { + episode.SeasonId = season.Id; + episode.SeasonName = season.Name; + } + + // Assume season 1 if there's no season folder and a season number could not be determined + if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue)) + { + episode.ParentIndexNumber = 1; } return episode; diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index a33a0826c9..2493796193 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -213,7 +213,7 @@ namespace Jellyfin.Api.Controllers if (item is IHasTrailers hasTrailers) { - var trailers = hasTrailers.GetTrailers(); + var trailers = hasTrailers.LocalTrailers; var dtosTrailers = _dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item); var allTrailers = new BaseItemDto[dtosExtras.Length + dtosTrailers.Count]; dtosExtras.CopyTo(allTrailers, 0); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index b1ac2fe8ee..ad20ea334f 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -40,9 +40,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem> { - /// <summary> - /// The trailer folder name. - /// </summary> + public const string TrailerFileName = "trailer"; public const string TrailersFolderName = "trailers"; public const string ThemeSongsFolderName = "theme-music"; public const string ThemeSongFileName = "theme"; @@ -99,8 +97,6 @@ namespace MediaBrowser.Controller.Entities }; private string _sortName; - private Guid[] _themeSongIds; - private Guid[] _themeVideoIds; private string _forcedSortName; @@ -121,40 +117,6 @@ namespace MediaBrowser.Controller.Entities ExtraIds = Array.Empty<Guid>(); } - [JsonIgnore] - public Guid[] ThemeSongIds - { - get - { - return _themeSongIds ??= GetExtras() - .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong) - .Select(song => song.Id) - .ToArray(); - } - - private set - { - _themeSongIds = value; - } - } - - [JsonIgnore] - public Guid[] ThemeVideoIds - { - get - { - return _themeVideoIds ??= GetExtras() - .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo) - .Select(song => song.Id) - .ToArray(); - } - - private set - { - _themeVideoIds = value; - } - } - [JsonIgnore] public string PreferredMetadataCountryCode { get; set; } @@ -1379,28 +1341,6 @@ namespace MediaBrowser.Controller.Entities }).OrderBy(i => i.Path).ToArray(); } - protected virtual BaseItem[] LoadExtras(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) - { - return fileSystemChildren - .Where(child => child.IsDirectory && AllExtrasTypesFolderNames.ContainsKey(child.Name)) - .SelectMany(folder => LibraryManager - .ResolvePaths(FileSystem.GetFiles(folder.FullName), directoryService, null, new LibraryOptions()) - .OfType<Video>() - .Select(video => - { - // Try to retrieve it from the db. If we don't find it, use the resolved version - if (LibraryManager.GetItemById(video.Id) is Video dbItem) - { - video = dbItem; - } - - video.ExtraType = AllExtrasTypesFolderNames[folder.Name]; - return video; - }) - .OrderBy(video => video.Path)) // Sort them so that the list can be easily compared for changes - .ToArray(); - } - public Task RefreshMetadata(CancellationToken cancellationToken) { return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken); @@ -1434,13 +1374,8 @@ namespace MediaBrowser.Controller.Entities GetFileSystemChildren(options.DirectoryService).ToList() : new List<FileSystemMetadata>(); - var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); + requiresSave = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh - - if (ownedItemsChanged) - { - requiresSave = true; - } } catch (Exception ex) { @@ -1516,35 +1451,12 @@ namespace MediaBrowser.Controller.Entities /// <returns><c>true</c> if any items have changed, else <c>false</c>.</returns> protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { - var themeSongsChanged = false; - - var themeVideosChanged = false; - - var extrasChanged = false; - - var localTrailersChanged = false; - - if (IsFileProtocol && SupportsOwnedItems) + if (!IsFileProtocol || !SupportsOwnedItems || IsInMixedFolder || this is ICollectionFolder) { - if (SupportsThemeMedia) - { - if (!IsInMixedFolder) - { - themeSongsChanged = await RefreshThemeSongs(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - - themeVideosChanged = await RefreshThemeVideos(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - - extrasChanged = await RefreshExtras(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - } - } - - if (this is IHasTrailers hasTrailers) - { - localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - } + return false; } - return themeSongsChanged || themeVideosChanged || extrasChanged || localTrailersChanged; + return await RefreshExtras(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); } protected virtual FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) @@ -1554,98 +1466,24 @@ namespace MediaBrowser.Controller.Entities return directoryService.GetFileSystemEntries(path); } - private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) - { - var newItems = LibraryManager.FindTrailers(this, fileSystemChildren, options.DirectoryService); - - var newItemIds = newItems.Select(i => i.Id); - - var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds); - var ownerId = item.Id; - - var tasks = newItems.Select(i => - { - var subOptions = new MetadataRefreshOptions(options); - - if (i.ExtraType != Model.Entities.ExtraType.Trailer || - i.OwnerId != ownerId || - !i.ParentId.Equals(Guid.Empty)) - { - i.ExtraType = Model.Entities.ExtraType.Trailer; - i.OwnerId = ownerId; - i.ParentId = Guid.Empty; - subOptions.ForceSave = true; - } - - return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken); - }); - - await Task.WhenAll(tasks).ConfigureAwait(false); - - item.LocalTrailerIds = newItemIds.ToArray(); - - return itemsChanged; - } - private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { - var extras = LoadExtras(fileSystemChildren, options.DirectoryService); - var themeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService); - var themeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService); - var newExtras = new BaseItem[extras.Length + themeVideos.Length + themeSongs.Length]; - extras.CopyTo(newExtras, 0); - themeVideos.CopyTo(newExtras, extras.Length); - themeSongs.CopyTo(newExtras, extras.Length + themeVideos.Length); - - var newExtraIds = newExtras.Select(i => i.Id).ToArray(); - + var extras = LibraryManager.FindExtras(item, fileSystemChildren).ToArray(); + var newExtraIds = extras.Select(i => i.Id).ToArray(); var extrasChanged = !item.ExtraIds.SequenceEqual(newExtraIds); - if (extrasChanged) + if (!extrasChanged) { - var ownerId = item.Id; - - var tasks = newExtras.Select(i => - { - var subOptions = new MetadataRefreshOptions(options); - if (i.OwnerId != ownerId || i.ParentId != Guid.Empty) - { - i.OwnerId = ownerId; - i.ParentId = Guid.Empty; - subOptions.ForceSave = true; - } - - return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken); - }); - - await Task.WhenAll(tasks).ConfigureAwait(false); - - item.ExtraIds = newExtraIds; + return false; } - return extrasChanged; - } - - private async Task<bool> RefreshThemeVideos(BaseItem item, MetadataRefreshOptions options, IEnumerable<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) - { - var newThemeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService); - - var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToArray(); - - var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds); - var ownerId = item.Id; - var tasks = newThemeVideos.Select(i => + var tasks = extras.Select(i => { var subOptions = new MetadataRefreshOptions(options); - - if (!i.ExtraType.HasValue || - i.ExtraType.Value != Model.Entities.ExtraType.ThemeVideo || - i.OwnerId != ownerId || - !i.ParentId.Equals(Guid.Empty)) + if (i.OwnerId != ownerId || i.ParentId != Guid.Empty) { - i.ExtraType = Model.Entities.ExtraType.ThemeVideo; i.OwnerId = ownerId; i.ParentId = Guid.Empty; subOptions.ForceSave = true; @@ -1656,48 +1494,9 @@ namespace MediaBrowser.Controller.Entities await Task.WhenAll(tasks).ConfigureAwait(false); - // They are expected to be sorted by SortName - item.ThemeVideoIds = newThemeVideos.OrderBy(i => i.SortName).Select(i => i.Id).ToArray(); + item.ExtraIds = newExtraIds; - return themeVideosChanged; - } - - /// <summary> - /// Refreshes the theme songs. - /// </summary> - private async Task<bool> RefreshThemeSongs(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) - { - var newThemeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService); - var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToArray(); - - var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds); - - var ownerId = item.Id; - - var tasks = newThemeSongs.Select(i => - { - var subOptions = new MetadataRefreshOptions(options); - - if (!i.ExtraType.HasValue || - i.ExtraType.Value != Model.Entities.ExtraType.ThemeSong || - i.OwnerId != ownerId || - !i.ParentId.Equals(Guid.Empty)) - { - i.ExtraType = Model.Entities.ExtraType.ThemeSong; - i.OwnerId = ownerId; - i.ParentId = Guid.Empty; - subOptions.ForceSave = true; - } - - return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken); - }); - - await Task.WhenAll(tasks).ConfigureAwait(false); - - // They are expected to be sorted by SortName - item.ThemeSongIds = newThemeSongs.OrderBy(i => i.SortName).Select(i => i.Id).ToArray(); - - return themeSongsChanged; + return true; } public string GetPresentationUniqueKey() @@ -2891,14 +2690,14 @@ namespace MediaBrowser.Controller.Entities StringComparison.OrdinalIgnoreCase); } - public IEnumerable<BaseItem> GetThemeSongs() + public IReadOnlyList<BaseItem> GetThemeSongs() { - return ThemeSongIds.Select(LibraryManager.GetItemById); + return GetExtras().Where(e => e.ExtraType == Model.Entities.ExtraType.ThemeSong).ToArray(); } - public IEnumerable<BaseItem> GetThemeVideos() + public IReadOnlyList<BaseItem> GetThemeVideos() { - return ThemeVideoIds.Select(LibraryManager.GetItemById); + return GetExtras().Where(e => e.ExtraType == Model.Entities.ExtraType.ThemeVideo).ToArray(); } /// <summary> diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs index f4271678d4..bb4a6ea94a 100644 --- a/MediaBrowser.Controller/Entities/IHasTrailers.cs +++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs @@ -2,7 +2,6 @@ #pragma warning disable CS1591 -using System; using System.Collections.Generic; using MediaBrowser.Model.Entities; @@ -17,18 +16,10 @@ namespace MediaBrowser.Controller.Entities IReadOnlyList<MediaUrl> RemoteTrailers { get; set; } /// <summary> - /// Gets or sets the local trailer ids. + /// Gets the local trailers. /// </summary> - /// <value>The local trailer ids.</value> - IReadOnlyList<Guid> LocalTrailerIds { get; set; } - - /// <summary> - /// Gets or sets the remote trailer ids. - /// </summary> - /// <value>The remote trailer ids.</value> - IReadOnlyList<Guid> RemoteTrailerIds { get; set; } - - Guid Id { get; set; } + /// <value>The local trailers.</value> + IReadOnlyList<BaseItem> LocalTrailers { get; } } /// <summary> @@ -42,57 +33,6 @@ namespace MediaBrowser.Controller.Entities /// <param name="item">Media item.</param> /// <returns><see cref="IReadOnlyList{Guid}" />.</returns> public static int GetTrailerCount(this IHasTrailers item) - => item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count; - - /// <summary> - /// Gets the trailer ids. - /// </summary> - /// <param name="item">Media item.</param> - /// <returns><see cref="IReadOnlyList{Guid}" />.</returns> - public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item) - { - var localIds = item.LocalTrailerIds; - var remoteIds = item.RemoteTrailerIds; - - var all = new Guid[localIds.Count + remoteIds.Count]; - var index = 0; - foreach (var id in localIds) - { - all[index++] = id; - } - - foreach (var id in remoteIds) - { - all[index++] = id; - } - - return all; - } - - /// <summary> - /// Gets the trailers. - /// </summary> - /// <param name="item">Media item.</param> - /// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns> - public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item) - { - var localIds = item.LocalTrailerIds; - var remoteIds = item.RemoteTrailerIds; - var libraryManager = BaseItem.LibraryManager; - - var all = new BaseItem[localIds.Count + remoteIds.Count]; - var index = 0; - foreach (var id in localIds) - { - all[index++] = libraryManager.GetItemById(id); - } - - foreach (var id in remoteIds) - { - all[index++] = libraryManager.GetItemById(id); - } - - return all; - } + => item.LocalTrailers.Count + item.RemoteTrailers.Count; } } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index e46f99cd57..6b93d8d87a 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -9,7 +9,6 @@ using System.Text.Json.Serialization; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Entities.Movies @@ -21,10 +20,6 @@ namespace MediaBrowser.Controller.Entities.Movies { public BoxSet() { - RemoteTrailers = Array.Empty<MediaUrl>(); - LocalTrailerIds = Array.Empty<Guid>(); - RemoteTrailerIds = Array.Empty<Guid>(); - DisplayOrder = ItemSortBy.PremiereDate; } @@ -38,10 +33,9 @@ namespace MediaBrowser.Controller.Entities.Movies public override bool SupportsPeople => true; /// <inheritdoc /> - public IReadOnlyList<Guid> LocalTrailerIds { get; set; } - - /// <inheritdoc /> - public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } + public IReadOnlyList<BaseItem> LocalTrailers => GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer) + .ToArray(); /// <summary> /// Gets or sets the display order. diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index b54bbf5eb9..6f1a0a8cfe 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -7,12 +7,9 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; namespace MediaBrowser.Controller.Entities.Movies @@ -22,22 +19,29 @@ namespace MediaBrowser.Controller.Entities.Movies /// </summary> public class Movie : Video, IHasSpecialFeatures, IHasTrailers, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping { - public Movie() + private IReadOnlyList<Guid> _specialFeatureIds; + + /// <inheritdoc /> + public IReadOnlyList<Guid> SpecialFeatureIds { - SpecialFeatureIds = Array.Empty<Guid>(); - RemoteTrailers = Array.Empty<MediaUrl>(); - LocalTrailerIds = Array.Empty<Guid>(); - RemoteTrailerIds = Array.Empty<Guid>(); + get + { + return _specialFeatureIds ??= GetExtras() + .Where(extra => extra.ExtraType != Model.Entities.ExtraType.Trailer) + .Select(song => song.Id) + .ToArray(); + } + + set + { + _specialFeatureIds = value; + } } /// <inheritdoc /> - public IReadOnlyList<Guid> SpecialFeatureIds { get; set; } - - /// <inheritdoc /> - public IReadOnlyList<Guid> LocalTrailerIds { get; set; } - - /// <inheritdoc /> - public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } + public IReadOnlyList<BaseItem> LocalTrailers => GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer) + .ToArray(); /// <summary> /// Gets or sets the name of the TMDB collection. @@ -66,54 +70,6 @@ namespace MediaBrowser.Controller.Entities.Movies return 2.0 / 3; } - protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) - { - var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - - // Must have a parent to have special features - // In other words, it must be part of the Parent/Child tree - if (IsFileProtocol && SupportsOwnedItems && !IsInMixedFolder) - { - var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - - if (specialFeaturesChanged) - { - hasChanges = true; - } - } - - return hasChanges; - } - - private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) - { - var newItems = LibraryManager.FindExtras(this, fileSystemChildren, options.DirectoryService).ToList(); - var newItemIds = newItems.Select(i => i.Id).ToArray(); - - var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds); - - var ownerId = Id; - - var tasks = newItems.Select(i => - { - var subOptions = new MetadataRefreshOptions(options); - - if (i.OwnerId != ownerId) - { - i.OwnerId = ownerId; - subOptions.ForceSave = true; - } - - return RefreshMetadataForOwnedItem(i, false, subOptions, cancellationToken); - }); - - await Task.WhenAll(tasks).ConfigureAwait(false); - - SpecialFeatureIds = newItemIds; - - return itemsChanged; - } - /// <inheritdoc /> public override UnratedItem GetBlockUnratedType() { diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 27c3ff81bd..dcc752f8c7 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -20,18 +20,10 @@ namespace MediaBrowser.Controller.Entities.TV /// </summary> public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries { - public Episode() - { - RemoteTrailers = Array.Empty<MediaUrl>(); - LocalTrailerIds = Array.Empty<Guid>(); - RemoteTrailerIds = Array.Empty<Guid>(); - } - /// <inheritdoc /> - public IReadOnlyList<Guid> LocalTrailerIds { get; set; } - - /// <inheritdoc /> - public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } + public IReadOnlyList<BaseItem> LocalTrailers => GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer) + .ToArray(); /// <summary> /// Gets or sets the season in which it aired. diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index e4933e9682..90fcffe326 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -27,9 +27,6 @@ namespace MediaBrowser.Controller.Entities.TV { public Series() { - RemoteTrailers = Array.Empty<MediaUrl>(); - LocalTrailerIds = Array.Empty<Guid>(); - RemoteTrailerIds = Array.Empty<Guid>(); AirDays = Array.Empty<DayOfWeek>(); } @@ -53,10 +50,9 @@ namespace MediaBrowser.Controller.Entities.TV public override bool SupportsPeople => true; /// <inheritdoc /> - public IReadOnlyList<Guid> LocalTrailerIds { get; set; } - - /// <inheritdoc /> - public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } + public IReadOnlyList<BaseItem> LocalTrailers => GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer) + .ToArray(); /// <summary> /// Gets or sets the display order. diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 1cff720370..25511f9d9d 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -745,10 +745,9 @@ namespace MediaBrowser.Controller.Entities var val = query.HasTrailer.Value; var trailerCount = 0; - var hasTrailers = item as IHasTrailers; - if (hasTrailers != null) + if (item is IHasTrailers hasTrailers) { - trailerCount = hasTrailers.GetTrailerIds().Count; + trailerCount = hasTrailers.GetTrailerCount(); } var ok = val ? trailerCount > 0 : trailerCount == 0; @@ -763,7 +762,7 @@ namespace MediaBrowser.Controller.Entities { var filterValue = query.HasThemeSong.Value; - var themeCount = item.ThemeSongIds.Length; + var themeCount = item.GetThemeSongs().Count; var ok = filterValue ? themeCount > 0 : themeCount == 0; if (!ok) @@ -776,7 +775,7 @@ namespace MediaBrowser.Controller.Entities { var filterValue = query.HasThemeVideo.Value; - var themeCount = item.ThemeVideoIds.Length; + var themeCount = item.GetThemeVideos().Count; var ok = filterValue ? themeCount > 0 : themeCount == 0; if (!ok) diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 1e1e2adb87..1ae28abde5 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Emby.Naming.Common; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; @@ -426,29 +425,15 @@ namespace MediaBrowser.Controller.Library /// <returns>Guid.</returns> Guid GetNewItemId(string key, Type type); - /// <summary> - /// Finds the trailers. - /// </summary> - /// <param name="owner">The owner.</param> - /// <param name="fileSystemChildren">The file system children.</param> - /// <param name="directoryService">The directory service.</param> - /// <returns>IEnumerable<Trailer>.</returns> - IEnumerable<Video> FindTrailers( - BaseItem owner, - List<FileSystemMetadata> fileSystemChildren, - IDirectoryService directoryService); - /// <summary> /// Finds the extras. /// </summary> /// <param name="owner">The owner.</param> /// <param name="fileSystemChildren">The file system children.</param> - /// <param name="directoryService">The directory service.</param> /// <returns>IEnumerable<Video>.</returns> IEnumerable<Video> FindExtras( BaseItem owner, - List<FileSystemMetadata> fileSystemChildren, - IDirectoryService directoryService); + List<FileSystemMetadata> fileSystemChildren); /// <summary> /// Gets the collection folders. diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index f791478038..46f1fede3e 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -106,7 +106,7 @@ namespace MediaBrowser.LocalMetadata.Images { if (!item.IsFileProtocol) { - return Enumerable.Empty<FileSystemMetadata>(); + yield break; } var path = item.ContainingFolderPath; @@ -114,20 +114,21 @@ namespace MediaBrowser.LocalMetadata.Images // Exit if the cache dir does not exist, alternative solution is to create it, but that's a lot of empty dirs... if (!Directory.Exists(path)) { - return Enumerable.Empty<FileSystemMetadata>(); + yield break; } - if (includeDirectories) + var files = directoryService.GetFileSystemEntries(path).OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty)); + var count = BaseItem.SupportedImageExtensions.Length; + foreach (var file in files) { - return directoryService.GetFileSystemEntries(path) - .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase) || i.IsDirectory) - - .OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty)); + for (var i = 0; i < count; i++) + { + if ((includeDirectories && file.IsDirectory) || string.Equals(BaseItem.SupportedImageExtensions[i], file.Extension, StringComparison.OrdinalIgnoreCase)) + { + yield return file; + } + } } - - return directoryService.GetFiles(path) - .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase)) - .OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty)); } /// <inheritdoc /> diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index d13e89cee8..8dd637559b 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -81,9 +81,7 @@ namespace Jellyfin.Naming.Tests.Video private void Test(string input, ExtraType? expectedType) { - var parser = GetExtraTypeParser(_videoOptions); - - var extraType = parser.GetExtraInfo(input).ExtraType; + var extraType = ExtraResolver.GetExtraInfo(input, _videoOptions).ExtraType; Assert.Equal(expectedType, extraType); } @@ -93,14 +91,9 @@ namespace Jellyfin.Naming.Tests.Video { var rule = new ExtraRule(ExtraType.Unknown, ExtraRuleType.Regex, @"([eE]x(tra)?\.\w+)", MediaType.Video); var options = new NamingOptions { VideoExtraRules = new[] { rule } }; - var res = GetExtraTypeParser(options).GetExtraInfo("extra.mp4"); + var res = ExtraResolver.GetExtraInfo("extra.mp4", options); Assert.Equal(rule, res.Rule); } - - private ExtraResolver GetExtraTypeParser(NamingOptions videoOptions) - { - return new ExtraResolver(videoOptions); - } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index d02f8ae924..323457d094 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -30,8 +30,8 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); - Assert.Single(result[0].Extras); + Assert.Single(result.Where(v => v.ExtraType == null)); + Assert.Single(result.Where(v => v.ExtraType != null)); } [Fact] @@ -53,8 +53,8 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); - Assert.Single(result[0].Extras); + Assert.Single(result.Where(v => v.ExtraType == null)); + Assert.Single(result.Where(v => v.ExtraType != null)); Assert.Equal(2, result[0].AlternateVersions.Count); } @@ -102,7 +102,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Equal(7, result.Count); - Assert.Empty(result[0].Extras); Assert.Empty(result[0].AlternateVersions); } @@ -130,7 +129,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Single(result); - Assert.Empty(result[0].Extras); Assert.Equal(7, result[0].AlternateVersions.Count); } @@ -159,7 +157,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Equal(9, result.Count); - Assert.Empty(result[0].Extras); Assert.Empty(result[0].AlternateVersions); } @@ -184,7 +181,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Equal(5, result.Count); - Assert.Empty(result[0].Extras); Assert.Empty(result[0].AlternateVersions); } @@ -211,7 +207,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Equal(5, result.Count); - Assert.Empty(result[0].Extras); Assert.Empty(result[0].AlternateVersions); } @@ -239,7 +234,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Single(result); - Assert.Empty(result[0].Extras); Assert.Equal(7, result[0].AlternateVersions.Count); Assert.False(result[0].AlternateVersions[2].Is3D); Assert.True(result[0].AlternateVersions[3].Is3D); @@ -270,7 +264,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Single(result); - Assert.Empty(result[0].Extras); Assert.Equal(7, result[0].AlternateVersions.Count); Assert.False(result[0].AlternateVersions[3].Is3D); Assert.True(result[0].AlternateVersions[4].Is3D); @@ -320,7 +313,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Equal(7, result.Count); - Assert.Empty(result[0].Extras); Assert.Empty(result[0].AlternateVersions); } @@ -347,7 +339,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Equal(5, result.Count); - Assert.Empty(result[0].Extras); Assert.Empty(result[0].AlternateVersions); } @@ -369,7 +360,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Single(result); - Assert.Empty(result[0].Extras); Assert.Single(result[0].AlternateVersions); } @@ -391,7 +381,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Single(result); - Assert.Empty(result[0].Extras); Assert.Single(result[0].AlternateVersions); } @@ -413,7 +402,6 @@ namespace Jellyfin.Naming.Tests.Video _namingOptions).ToList(); Assert.Single(result); - Assert.Empty(result[0].Extras); Assert.Single(result[0].AlternateVersions); } diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs index 8794d3ebe8..41da0e0771 100644 --- a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs @@ -22,9 +22,7 @@ namespace Jellyfin.Naming.Tests.Video "Bad Boys (2006)-trailer.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Single(result); TestStackInfo(result[0], "Bad Boys (2006)", 4); @@ -39,9 +37,7 @@ namespace Jellyfin.Naming.Tests.Video "Bad Boys (2007).mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Empty(result); } @@ -55,9 +51,7 @@ namespace Jellyfin.Naming.Tests.Video "Bad Boys 2007.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Empty(result); } @@ -71,9 +65,7 @@ namespace Jellyfin.Naming.Tests.Video "300 (2007).mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Empty(result); } @@ -87,9 +79,7 @@ namespace Jellyfin.Naming.Tests.Video "300 2007.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Empty(result); } @@ -103,9 +93,7 @@ namespace Jellyfin.Naming.Tests.Video "Star Trek 2- The wrath of khan.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Empty(result); } @@ -119,9 +107,7 @@ namespace Jellyfin.Naming.Tests.Video "Red Riding in the Year of Our Lord 1974 (2009).mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Empty(result); } @@ -135,9 +121,7 @@ namespace Jellyfin.Naming.Tests.Video "d:/movies/300 2006 part2.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Single(result); TestStackInfo(result[0], "300 2006", 2); @@ -155,9 +139,7 @@ namespace Jellyfin.Naming.Tests.Video "Bad Boys (2006)-trailer.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Single(result); TestStackInfo(result[0], "Bad Boys (2006).stv.unrated.multi.1080p.bluray.x264-rough", 4); @@ -175,9 +157,7 @@ namespace Jellyfin.Naming.Tests.Video "Bad Boys (2006)-trailer.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Empty(result); } @@ -194,9 +174,7 @@ namespace Jellyfin.Naming.Tests.Video "300 (2006)-trailer.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Single(result); TestStackInfo(result[0], "300 (2006)", 4); @@ -214,9 +192,7 @@ namespace Jellyfin.Naming.Tests.Video "Bad Boys (2006)-trailer.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Single(result); TestStackInfo(result[0], "Bad Boys (2006)", 3); @@ -238,9 +214,7 @@ namespace Jellyfin.Naming.Tests.Video "300 (2006)-trailer.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Equal(2, result.Count); TestStackInfo(result[1], "Bad Boys (2006)", 4); @@ -256,9 +230,7 @@ namespace Jellyfin.Naming.Tests.Video "blah blah - cd 2" }; - var resolver = GetResolver(); - - var result = resolver.ResolveDirectories(files).ToList(); + var result = StackResolver.ResolveDirectories(files, _namingOptions).ToList(); Assert.Single(result); TestStackInfo(result[0], "blah blah", 2); @@ -275,9 +247,7 @@ namespace Jellyfin.Naming.Tests.Video "300-trailer.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Single(result); @@ -297,9 +267,7 @@ namespace Jellyfin.Naming.Tests.Video "Avengers part3.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -328,9 +296,7 @@ namespace Jellyfin.Naming.Tests.Video "300-trailer.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Equal(3, result.Count); @@ -354,9 +320,7 @@ namespace Jellyfin.Naming.Tests.Video "300 (2006)-trailer.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Single(result); @@ -375,9 +339,7 @@ namespace Jellyfin.Naming.Tests.Video new FileSystemMetadata { FullName = "300 (2006) part1", IsDirectory = true } }; - var resolver = GetResolver(); - - var result = resolver.Resolve(files).ToList(); + var result = StackResolver.Resolve(files, _namingOptions).ToList(); Assert.Equal(2, result.Count); TestStackInfo(result[0], "300 (2006)", 3); @@ -397,9 +359,7 @@ namespace Jellyfin.Naming.Tests.Video "Harry Potter and the Deathly Hallows 4.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Empty(result); } @@ -414,9 +374,7 @@ namespace Jellyfin.Naming.Tests.Video "Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part2.mkv" }; - var resolver = GetResolver(); - - var result = resolver.ResolveFiles(files).ToList(); + var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); Assert.Single(result); Assert.Equal(2, result[0].Files.Count); @@ -432,9 +390,7 @@ namespace Jellyfin.Naming.Tests.Video @"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 02)" }; - var resolver = GetResolver(); - - var result = resolver.ResolveDirectories(files).ToList(); + var result = StackResolver.ResolveDirectories(files, _namingOptions).ToList(); Assert.Single(result); Assert.Equal(2, result[0].Files.Count); @@ -445,10 +401,5 @@ namespace Jellyfin.Naming.Tests.Video Assert.Equal(fileCount, stack.Files.Count); Assert.Equal(name, stack.Name); } - - private StackResolver GetResolver() - { - return new StackResolver(_namingOptions); - } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index 9e0776c3cd..5d9ef1340c 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using Emby.Naming.Common; using Emby.Naming.Video; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using Xunit; @@ -48,16 +49,25 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Equal(5, result.Count); + Assert.Equal(11, result.Count); var batman = result.FirstOrDefault(x => string.Equals(x.Name, "Batman", StringComparison.Ordinal)); Assert.NotNull(batman); Assert.Equal(3, batman!.Files.Count); - Assert.Equal(3, batman!.Extras.Count); var harry = result.FirstOrDefault(x => string.Equals(x.Name, "Harry Potter and the Deathly Hallows", StringComparison.Ordinal)); Assert.NotNull(harry); Assert.Equal(4, harry!.Files.Count); - Assert.Equal(2, harry!.Extras.Count); + + Assert.False(result[2].ExtraType.HasValue); + + Assert.Equal(ExtraType.Trailer, result[3].ExtraType); + Assert.Equal(ExtraType.Trailer, result[4].ExtraType); + Assert.Equal(ExtraType.DeletedScene, result[5].ExtraType); + Assert.Equal(ExtraType.Sample, result[6].ExtraType); + Assert.Equal(ExtraType.Trailer, result[7].ExtraType); + Assert.Equal(ExtraType.Trailer, result[8].ExtraType); + Assert.Equal(ExtraType.Trailer, result[9].ExtraType); + Assert.Equal(ExtraType.Trailer, result[10].ExtraType); } [Fact] @@ -97,7 +107,8 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } [Fact] @@ -117,7 +128,8 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } [Fact] @@ -138,15 +150,18 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); + Assert.Equal(ExtraType.Trailer, result[2].ExtraType); } [Fact] - public void TestDifferentNames() + public void Resolve_SameNameAndYear_ReturnsSingleItem() { var files = new[] { "Looper (2012)-trailer.mkv", + "Looper 2012-trailer.mkv", "Looper.2012.bluray.720p.x264.mkv" }; @@ -158,7 +173,30 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); + Assert.Equal(ExtraType.Trailer, result[2].ExtraType); + } + + [Fact] + public void Resolve_TrailerMatchesFolderName_ReturnsSingleItem() + { + var files = new[] + { + "/movies/Looper (2012)/Looper (2012)-trailer.mkv", + "/movies/Looper (2012)/Looper.bluray.720p.x264.mkv" + }; + + var result = VideoListResolver.Resolve( + files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + }).ToList(), + _namingOptions).ToList(); + + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } [Fact] @@ -233,27 +271,7 @@ namespace Jellyfin.Naming.Tests.Video { @"No (2012) part1.mp4", @"No (2012) part2.mp4", - @"No (2012) part1-trailer.mp4" - }; - - var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), - _namingOptions).ToList(); - - Assert.Single(result); - } - - [Fact] - public void TestStackedWithTrailer2() - { - var files = new[] - { - @"No (2012) part1.mp4", - @"No (2012) part2.mp4", + @"No (2012) part1-trailer.mp4", @"No (2012)-trailer.mp4" }; @@ -265,7 +283,10 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); + Assert.Equal(3, result.Count); + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); + Assert.Equal(ExtraType.Trailer, result[2].ExtraType); } [Fact] @@ -276,7 +297,7 @@ namespace Jellyfin.Naming.Tests.Video @"/Movies/Top Gun (1984)/movie.mp4", @"/Movies/Top Gun (1984)/Top Gun (1984)-trailer.mp4", @"/Movies/Top Gun (1984)/Top Gun (1984)-trailer2.mp4", - @"trailer.mp4" + @"/Movies/trailer.mp4" }; var result = VideoListResolver.Resolve( @@ -287,7 +308,10 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); + Assert.Equal(ExtraType.Trailer, result[2].ExtraType); + Assert.Equal(ExtraType.Trailer, result[3].ExtraType); } [Fact] @@ -396,7 +420,7 @@ namespace Jellyfin.Naming.Tests.Video var files = new[] { @"/Server/Despicable Me/Despicable Me (2010).mkv", - @"/Server/Despicable Me/movie-trailer.mkv" + @"/Server/Despicable Me/trailer.mkv" }; var result = VideoListResolver.Resolve( @@ -407,18 +431,17 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } [Fact] - public void TestTrailerFalsePositives() + public void Resolve_TrailerInTrailersFolder_ReturnsCorrectExtraType() { var files = new[] { - @"/Server/Despicable Me/Skyscraper (2018) - Big Game Spot.mkv", - @"/Server/Despicable Me/Skyscraper (2018) - Trailer.mkv", - @"/Server/Despicable Me/Baywatch (2017) - Big Game Spot.mkv", - @"/Server/Despicable Me/Baywatch (2017) - Trailer.mkv" + @"/Server/Despicable Me/Despicable Me (2010).mkv", + @"/Server/Despicable Me/trailers/some title.mkv" }; var result = VideoListResolver.Resolve( @@ -429,7 +452,8 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Equal(4, result.Count); + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } [Fact] @@ -449,7 +473,8 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); - Assert.Single(result); + Assert.False(result[0].ExtraType.HasValue); + Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } [Fact] diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs index a0fe4a5cf8..362c3216f2 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs @@ -1,4 +1,5 @@ -using Emby.Server.Implementations.Library.Resolvers.TV; +using Emby.Naming.Common; +using Emby.Server.Implementations.Library.Resolvers.TV; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -13,12 +14,14 @@ namespace Jellyfin.Server.Implementations.Tests.Library { public class EpisodeResolverTest { + private static readonly NamingOptions _namingOptions = new (); + [Fact] public void Resolve_GivenVideoInExtrasFolder_DoesNotResolveToEpisode() { var parent = new Folder { Name = "extras" }; - var episodeResolver = new EpisodeResolver(null); + var episodeResolver = new EpisodeResolver(_namingOptions); var itemResolveArgs = new ItemResolveArgs( Mock.Of<IServerApplicationPaths>(), Mock.Of<IDirectoryService>()) @@ -41,14 +44,14 @@ namespace Jellyfin.Server.Implementations.Tests.Library // Have to create a mock because of moq proxies not being castable to a concrete implementation // https://github.com/jellyfin/jellyfin/blob/ab0cff8556403e123642dc9717ba778329554634/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs#L48 - var episodeResolver = new EpisodeResolverMock(); + var episodeResolver = new EpisodeResolverMock(_namingOptions); var itemResolveArgs = new ItemResolveArgs( Mock.Of<IServerApplicationPaths>(), Mock.Of<IDirectoryService>()) { Parent = series, CollectionType = CollectionType.TvShows, - FileInfo = new FileSystemMetadata() + FileInfo = new FileSystemMetadata { FullName = "Extras/Extras S01E01.mkv" } @@ -58,7 +61,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library private class EpisodeResolverMock : EpisodeResolver { - public EpisodeResolverMock() : base(null) + public EpisodeResolverMock(NamingOptions namingOptions) : base(namingOptions) { } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs new file mode 100644 index 0000000000..10afadbeff --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs @@ -0,0 +1,178 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AutoFixture; +using AutoFixture.AutoMoq; +using Emby.Naming.Common; +using Emby.Server.Implementations.Library.Resolvers; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Library.LibraryManager; + +public class FindExtrasTests +{ + private readonly Emby.Server.Implementations.Library.LibraryManager _libraryManager; + + public FindExtrasTests() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + fixture.Register(() => new NamingOptions()); + var configMock = fixture.Freeze<Mock<IServerConfigurationManager>>(); + configMock.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data"); + var fileSystemMock = fixture.Freeze<Mock<IFileSystem>>(); + fileSystemMock.Setup(f => f.GetFileInfo(It.IsAny<string>())).Returns<string>(path => new FileSystemMetadata { FullName = path }); + _libraryManager = fixture.Build<Emby.Server.Implementations.Library.LibraryManager>().Do(s => s.AddParts( + fixture.Create<IEnumerable<IResolverIgnoreRule>>(), + new List<IItemResolver> { new GenericVideoResolver<Video>(fixture.Create<NamingOptions>()) }, + fixture.Create<IEnumerable<IIntroProvider>>(), + fixture.Create<IEnumerable<IBaseItemComparer>>(), + fixture.Create<IEnumerable<ILibraryPostScanTask>>())) + .Create(); + + // This is pretty terrible but unavoidable + BaseItem.FileSystem ??= fixture.Create<IFileSystem>(); + BaseItem.MediaSourceManager ??= fixture.Create<IMediaSourceManager>(); + } + + [Fact] + public void FindExtras_SeparateMovieFolder_FindsCorrectExtras() + { + var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" }; + var paths = new List<string> + { + "/movies/Up/Up.mkv", + "/movies/Up/Up - trailer.mkv", + "/movies/Up/Up - sample.mkv", + "/movies/Up/Up something else.mkv" + }; + + var files = paths.Select(p => new FileSystemMetadata + { + FullName = p, + IsDirectory = false + }).ToList(); + + var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + + Assert.Equal(2, extras.Count); + Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); + Assert.Equal(ExtraType.Sample, extras[1].ExtraType); + } + + [Fact] + public void FindExtras_SeparateMovieFolderWithMixedExtras_FindsCorrectExtras() + { + var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" }; + var paths = new List<string> + { + "/movies/Up/Up.mkv", + "/movies/Up/Up - trailer.mkv", + "/movies/Up/trailers/some trailer.mkv", + "/movies/Up/behind the scenes/the making of Up.mkv", + "/movies/Up/behind the scenes.mkv", + "/movies/Up/Up - sample.mkv", + "/movies/Up/Up something else.mkv" + }; + + var files = paths.Select(p => new FileSystemMetadata + { + FullName = p, + IsDirectory = false + }).ToList(); + + var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + + Assert.Equal(4, extras.Count); + Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); + Assert.Equal(ExtraType.Trailer, extras[1].ExtraType); + Assert.Equal(ExtraType.BehindTheScenes, extras[2].ExtraType); + Assert.Equal(ExtraType.Sample, extras[3].ExtraType); + } + + [Fact] + public void FindExtras_SeparateMovieFolderWithMixedExtras_FindsOnlyExtrasInMovieFolder() + { + var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" }; + var paths = new List<string> + { + "/movies/Up/Up.mkv", + "/movies/Up/trailer.mkv", + "/movies/Another Movie/trailer.mkv" + }; + + var files = paths.Select(p => new FileSystemMetadata + { + FullName = p, + IsDirectory = false + }).ToList(); + + var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + + Assert.Single(extras); + Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); + Assert.Equal("trailer", extras[0].FileNameWithoutExtension); + Assert.Equal("/movies/Up/trailer.mkv", extras[0].Path); + } + + [Fact] + public void FindExtras_SeparateMovieFolderWithParts_FindsCorrectExtras() + { + var owner = new Movie { Name = "Up", Path = "/movies/Up/Up - part1.mkv" }; + var paths = new List<string> + { + "/movies/Up/Up - part1.mkv", + "/movies/Up/Up - part2.mkv", + "/movies/Up/trailer.mkv", + "/movies/Another Movie/trailer.mkv" + }; + + var files = paths.Select(p => new FileSystemMetadata + { + FullName = p, + IsDirectory = false + }).ToList(); + + var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + + Assert.Single(extras); + Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); + Assert.Equal("trailer", extras[0].FileNameWithoutExtension); + Assert.Equal("/movies/Up/trailer.mkv", extras[0].Path); + } + + [Fact] + public void FindExtras_SeriesWithTrailers_FindsCorrectExtras() + { + var owner = new Series { Name = "Dexter", Path = "/series/Dexter" }; + var paths = new List<string> + { + "/series/Dexter/Season 1/S01E01.mkv", + "/series/Dexter/trailer.mkv", + "/series/Dexter/trailers/trailer2.mkv", + }; + + var files = paths.Select(p => new FileSystemMetadata + { + FullName = p, + IsDirectory = string.IsNullOrEmpty(Path.GetExtension(p)) + }).ToList(); + + var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + + Assert.Equal(2, extras.Count); + Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); + Assert.Equal("trailer", extras[0].FileNameWithoutExtension); + Assert.Equal("/series/Dexter/trailer.mkv", extras[0].Path); + Assert.Equal("/series/Dexter/trailers/trailer2.mkv", extras[1].Path); + } +} From 6030946d78cb52395a55a288ecdfb41890700cc7 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Tue, 7 Dec 2021 19:23:30 +0100 Subject: [PATCH 086/167] Fixes --- .../Library/Resolvers/Movies/MovieResolver.cs | 3 +-- .../Video/VideoListResolverTests.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 58279af9ef..de3c1e415a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -276,8 +276,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies var path = firstVideo.Path; if (video.ExtraType != null) { - // TODO - result.ExtraFiles.Add(files.First(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnoreCase))); + result.ExtraFiles.Add(files.Find(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnoreCase))); continue; } diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index 5d9ef1340c..b171d739a8 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -107,6 +107,7 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); + Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } @@ -128,6 +129,7 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); + Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } @@ -150,6 +152,7 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); + Assert.Equal(3, result.Count); Assert.False(result[0].ExtraType.HasValue); Assert.Equal(ExtraType.Trailer, result[1].ExtraType); Assert.Equal(ExtraType.Trailer, result[2].ExtraType); @@ -173,6 +176,7 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); + Assert.Equal(3, result.Count); Assert.False(result[0].ExtraType.HasValue); Assert.Equal(ExtraType.Trailer, result[1].ExtraType); Assert.Equal(ExtraType.Trailer, result[2].ExtraType); @@ -195,6 +199,7 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); + Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } @@ -308,6 +313,7 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); + Assert.Equal(4, result.Count); Assert.False(result[0].ExtraType.HasValue); Assert.Equal(ExtraType.Trailer, result[1].ExtraType); Assert.Equal(ExtraType.Trailer, result[2].ExtraType); @@ -431,6 +437,7 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); + Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } @@ -452,6 +459,7 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); + Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } @@ -473,6 +481,7 @@ namespace Jellyfin.Naming.Tests.Video }).ToList(), _namingOptions).ToList(); + Assert.Equal(2, result.Count); Assert.False(result[0].ExtraType.HasValue); Assert.Equal(ExtraType.Trailer, result[1].ExtraType); } From a327b43ab7faceadb555890c41f103008fc00737 Mon Sep 17 00:00:00 2001 From: Claus Vium <cvium@users.noreply.github.com> Date: Tue, 7 Dec 2021 20:28:51 +0100 Subject: [PATCH 087/167] Update MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs --- MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 8445a12aac..fc59e410fd 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -45,7 +45,6 @@ namespace MediaBrowser.Providers.MediaInfo private readonly FFProbeAudioInfo _audioProber; private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None); - private readonly NamingOptions _namingOptions; public FFProbeProvider( ILogger<FFProbeProvider> logger, From 3513f5a84bf21c64b5f909352e260bda2e9ab057 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Tue, 7 Dec 2021 17:10:27 -0700 Subject: [PATCH 088/167] Search for attribute text --- .../Library/PathExtensions.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 8ce054c38c..73a658186a 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -29,18 +29,19 @@ namespace Emby.Server.Implementations.Library } var openBracketIndex = str.IndexOf('['); - var equalsIndex = str.IndexOf('='); + var attributeIndex = str.IndexOf(attribute); var closingBracketIndex = str.IndexOf(']'); - while (openBracketIndex < equalsIndex && equalsIndex < closingBracketIndex) + while (openBracketIndex < attributeIndex && attributeIndex < closingBracketIndex) { - if (str[(openBracketIndex + 1)..equalsIndex].Equals(attribute, StringComparison.OrdinalIgnoreCase)) + if (openBracketIndex + 1 == attributeIndex + && str[attributeIndex + attribute.Length] == '=') { - return str[(equalsIndex + 1)..closingBracketIndex].Trim().ToString(); + return str[(attributeIndex + attribute.Length + 1)..closingBracketIndex].Trim().ToString(); } - str = str[(closingBracketIndex+ 1)..]; + str = str[(closingBracketIndex + 1)..]; openBracketIndex = str.IndexOf('['); - equalsIndex = str.IndexOf('='); + attributeIndex = str.IndexOf(attribute); closingBracketIndex = str.IndexOf(']'); } From 01a0a4a87ca6b89d5b235e5b240afb0abf44809d Mon Sep 17 00:00:00 2001 From: Jonas Resch <jonas.resch@live.de> Date: Wed, 8 Dec 2021 10:16:48 +0100 Subject: [PATCH 089/167] Add audioResolver argument to FFProbeVideoInfo initialization --- MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index fc59e410fd..19a435196a 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -74,7 +74,8 @@ namespace MediaBrowser.Providers.MediaInfo config, subtitleManager, chapterManager, - libraryManager); + libraryManager, + _audioResolver); _audioProber = new FFProbeAudioInfo(mediaSourceManager, mediaEncoder, itemRepo, libraryManager); } From d47811bdaf8bbfc74c8185ef0bff7490726828d9 Mon Sep 17 00:00:00 2001 From: Jonas Resch <jonas.resch@live.de> Date: Wed, 8 Dec 2021 10:17:25 +0100 Subject: [PATCH 090/167] Fix wrong ffmpeg map argument due to wrong calculation --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 91f6654bb7..92b345f126 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2007,7 +2007,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream.IsExternal) { int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; - int externalAudioStream = state.MediaSource.MediaStreams.FindIndex(i => i.Path == state.AudioStream.Path); + int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); args += string.Format( CultureInfo.InvariantCulture, From 4cdb590291086ee623ab8ed945f9707ade94777a Mon Sep 17 00:00:00 2001 From: Jonas Resch <jonas.resch@live.de> Date: Wed, 8 Dec 2021 10:18:09 +0100 Subject: [PATCH 091/167] Exclude .strm files when searching for external audio files --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index bec8ee34a6..b68b4c248c 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -131,7 +131,7 @@ namespace MediaBrowser.Providers.MediaInfo for (int i = 0; i < files.Count; i++) { string file = files[i]; - if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) || !AudioFileParser.IsAudioFile(file, _namingOptions)) + if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) || !AudioFileParser.IsAudioFile(file, _namingOptions) || Path.GetExtension(file).Equals(".strm", StringComparison.OrdinalIgnoreCase)) { continue; } From e18d966874cbdcab16ee99571d88147a9ac198fb Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Wed, 8 Dec 2021 16:49:20 +0100 Subject: [PATCH 092/167] Add "Async" suffix to AddExternalAudio method Co-authored-by: Claus Vium <cvium@users.noreply.github.com> --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 542e56256e..07d87e6076 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -586,7 +586,7 @@ namespace MediaBrowser.Providers.MediaInfo /// <param name="currentStreams">The current streams.</param> /// <param name="options">The refreshOptions.</param> /// <param name="cancellationToken">The cancellation token.</param> - private async Task AddExternalAudio( + private async Task AddExternalAudioAsync( Video video, List<MediaStream> currentStreams, MetadataRefreshOptions options, From 65833076dbc574cc830fdd370a3a6127cfdc3bcf Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Wed, 8 Dec 2021 16:49:27 +0100 Subject: [PATCH 093/167] Add "Async" suffix to AddExternalAudio method Co-authored-by: Claus Vium <cvium@users.noreply.github.com> --- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 07d87e6076..77372e0635 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -217,7 +217,7 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); - await AddExternalAudio(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); var libraryOptions = _libraryManager.GetLibraryOptions(video); From 03b3f08354f496d18444779977bfc1602bbb2c73 Mon Sep 17 00:00:00 2001 From: Jonas Resch <32968142+jonas-resch@users.noreply.github.com> Date: Wed, 8 Dec 2021 18:55:28 +0100 Subject: [PATCH 094/167] Format code in MediaBrowser.Providers/MediaInfo/AudioResolver.cs Co-authored-by: Claus Vium <cvium@users.noreply.github.com> --- MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs index b68b4c248c..425913501a 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -131,7 +131,9 @@ namespace MediaBrowser.Providers.MediaInfo for (int i = 0; i < files.Count; i++) { string file = files[i]; - if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) || !AudioFileParser.IsAudioFile(file, _namingOptions) || Path.GetExtension(file).Equals(".strm", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) + || !AudioFileParser.IsAudioFile(file, _namingOptions) + || Path.GetExtension(file.AsSpan()).Equals(".strm", StringComparison.OrdinalIgnoreCase)) { continue; } From ee80602a0d59f043b1901138e24241f74284cb65 Mon Sep 17 00:00:00 2001 From: Muhammed Aljailane <darhost56@gmail.com> Date: Tue, 7 Dec 2021 10:04:50 +0000 Subject: [PATCH 095/167] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 570d600fc3..9d4d40e512 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -15,7 +15,7 @@ "Favorites": "مفضلات", "Folders": "المجلدات", "Genres": "التضنيفات", - "HeaderAlbumArtists": "ألبوم الفنان", + "HeaderAlbumArtists": "فناني الألبوم", "HeaderContinueWatching": "استمر بالمشاهدة", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteArtists": "الفنانون المفضلون", From 8bd331f5fa07c0d6f4c394ed8adcf442587d6bae Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Tue, 7 Dec 2021 18:32:48 +0000 Subject: [PATCH 096/167] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hr/ --- Emby.Server.Implementations/Localization/Core/hr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index d7cda61da6..4df0444e6d 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -15,7 +15,7 @@ "Favorites": "Favoriti", "Folders": "Mape", "Genres": "Žanrovi", - "HeaderAlbumArtists": "Album od izvođača", + "HeaderAlbumArtists": "Izvođači albuma", "HeaderContinueWatching": "Nastavi gledati", "HeaderFavoriteAlbums": "Omiljeni albumi", "HeaderFavoriteArtists": "Omiljeni izvođači", From 8aef2cb282b0b1fab08590fe7b6c6a9ee79c09e0 Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Tue, 7 Dec 2021 18:35:17 +0000 Subject: [PATCH 097/167] Translated using Weblate (Lithuanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/ --- Emby.Server.Implementations/Localization/Core/lt-LT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index f3a131d405..f0a07f604c 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -1,7 +1,7 @@ { "Albums": "Albumai", "AppDeviceValues": "Programa: {0}, Įrenginys: {1}", - "Application": "Programa", + "Application": "Programėlė", "Artists": "Atlikėjai", "AuthenticationSucceededWithUserName": "{0} sėkmingai autentifikuota", "Books": "Knygos", From 6640f3f782de64b7e7017760522c086b5a1c9d99 Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Mon, 6 Dec 2021 08:49:27 +0000 Subject: [PATCH 098/167] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 94ee389d7c..f5131c7836 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -55,7 +55,7 @@ "NotificationOptionPluginInstalled": "Plugin telah dipasang", "NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang", "NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang", - "NotificationOptionServerRestartRequired": "Server restart required", + "NotificationOptionServerRestartRequired": "", "NotificationOptionTaskFailed": "Kegagalan tugas berjadual", "NotificationOptionUserLockedOut": "Pengguna telah dikunci", "NotificationOptionVideoPlayback": "Ulangmain video bermula", @@ -75,7 +75,7 @@ "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", - "Sync": "Sync", + "Sync": "", "System": "Sistem", "TvShows": "Tayangan TV", "User": "User", From 1acb9e1d45a289d15ce72cff8f7bcbb5d9c2f314 Mon Sep 17 00:00:00 2001 From: archon eleven <archoneleven@gmail.com> Date: Mon, 6 Dec 2021 08:48:09 +0000 Subject: [PATCH 099/167] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index f5131c7836..2e0fbc366c 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -66,11 +66,11 @@ "PluginInstalledWithName": "{0} telah dipasang", "PluginUninstalledWithName": "{0} telah dinyahpasang", "PluginUpdatedWithName": "{0} telah dikemaskini", - "ProviderValue": "Provider: {0}", + "ProviderValue": "Pembekal: {0}", "ScheduledTaskFailedWithName": "{0} gagal", "ScheduledTaskStartedWithName": "{0} bermula", "ServerNameNeedsToBeRestarted": "{0} perlu di ulangmula", - "Shows": "Series", + "Shows": "Tayangan", "Songs": "Lagu-lagu", "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", @@ -78,7 +78,7 @@ "Sync": "", "System": "Sistem", "TvShows": "Tayangan TV", - "User": "User", + "User": "Pengguna", "UserCreatedWithName": "Pengguna {0} telah diwujudkan", "UserDeletedWithName": "Pengguna {0} telah dipadamkan", "UserDownloadingItemWithValues": "{0} sedang memuat turun {1}", From f50b2f7959a584ec0292beaef869c550e2f45b2d Mon Sep 17 00:00:00 2001 From: Weevild <Filip.westman@gmail.com> Date: Mon, 6 Dec 2021 12:50:32 +0000 Subject: [PATCH 100/167] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- Emby.Server.Implementations/Localization/Core/sv.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index 1cef30b6c5..f3f6016615 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -96,8 +96,8 @@ "TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter", "TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.", "TaskRefreshChannels": "Uppdatera kanaler", - "TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.", - "TaskCleanTranscode": "Töm transkodningskatalog", + "TaskCleanTranscodeDescription": "Raderar omkodningsfiler som är mer än en dag gamla.", + "TaskCleanTranscode": "Töm omkodningskatalog", "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.", "TaskUpdatePlugins": "Uppdatera insticksprogram", "TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.", From 5c407dbab695ba9ef15299b781b288c873edb777 Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Tue, 7 Dec 2021 18:36:09 +0000 Subject: [PATCH 101/167] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ro/ --- Emby.Server.Implementations/Localization/Core/ro.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json index 510aac11c6..f8fad7b631 100644 --- a/Emby.Server.Implementations/Localization/Core/ro.json +++ b/Emby.Server.Implementations/Localization/Core/ro.json @@ -74,7 +74,7 @@ "HeaderFavoriteArtists": "Artiști Favoriți", "HeaderFavoriteAlbums": "Albume Favorite", "HeaderContinueWatching": "Vizionează în continuare", - "HeaderAlbumArtists": "Album Artiști", + "HeaderAlbumArtists": "Albume Artiști", "Genres": "Genuri", "Folders": "Dosare", "Favorites": "Favorite", From 13ac3e3665468bb43edb5e7b05f3059089ab8297 Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Tue, 7 Dec 2021 18:42:08 +0000 Subject: [PATCH 102/167] Translated using Weblate (Macedonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/ --- Emby.Server.Implementations/Localization/Core/mk.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json index d7839be57a..279734c5ee 100644 --- a/Emby.Server.Implementations/Localization/Core/mk.json +++ b/Emby.Server.Implementations/Localization/Core/mk.json @@ -97,5 +97,8 @@ "TasksChannelsCategory": "Интернет Канали", "TasksApplicationCategory": "Апликација", "TasksLibraryCategory": "Библиотека", - "TasksMaintenanceCategory": "Одржување" + "TasksMaintenanceCategory": "Одржување", + "Undefined": "Недефинирано", + "Forced": "Принудно", + "Default": "Зададено" } From 593b2fd359de378a2588a8d54d11ebc0bb2cd3f1 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Thu, 9 Dec 2021 08:06:06 -0700 Subject: [PATCH 103/167] Add more speed and more tests --- .../Library/PathExtensions.cs | 23 +++++++++++-------- .../Library/PathExtensionsTests.cs | 8 +++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 73a658186a..78850c149e 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -28,21 +28,26 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("String can't be empty.", nameof(attribute)); } - var openBracketIndex = str.IndexOf('['); var attributeIndex = str.IndexOf(attribute); - var closingBracketIndex = str.IndexOf(']'); - while (openBracketIndex < attributeIndex && attributeIndex < closingBracketIndex) + + // Must be at least 3 characters after the attribute =, ], any character. + var maxIndex = str.Length - attribute.Length - 3; + while (attributeIndex > -1 && attributeIndex < maxIndex) { - if (openBracketIndex + 1 == attributeIndex - && str[attributeIndex + attribute.Length] == '=') + var attributeEnd = attributeIndex + attribute.Length; + if (attributeIndex > 0 + && str[attributeIndex - 1] == '[' + && str[attributeEnd] == '=') { - return str[(attributeIndex + attribute.Length + 1)..closingBracketIndex].Trim().ToString(); + var closingIndex = str[attributeEnd..].IndexOf(']'); + if (closingIndex != -1) + { + return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString(); + } } - str = str[(closingBracketIndex + 1)..]; - openBracketIndex = str.IndexOf('['); + str = str[attributeEnd..]; attributeIndex = str.IndexOf(attribute); - closingBracketIndex = str.IndexOf(']'); } // for imdbid we also accept pattern matching diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index d6cbe96474..950f0e76d5 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -14,6 +14,14 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("Superman: Red Son [imdbid1=tt11111111][imdbid=tt10985510]", "imdbid", "tt10985510")] [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "imdbid", "tt10985510")] [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "tmdbid", "618355")] + [InlineData("[tmdbid=618355]", "tmdbid", "618355")] + [InlineData("tmdbid=618355][tmdbid=618355]", "tmdbid", "618355")] + [InlineData("[tmdbid=618355]tmdbid=618355]", "tmdbid", "618355")] + [InlineData("tmdbid=618355]", "tmdbid", null)] + [InlineData("[tmdbid=618355", "tmdbid", null)] + [InlineData("tmdbid=618355", "tmdbid", null)] + [InlineData("tmdbid=", "tmdbid", null)] + [InlineData("tmdbid", "tmdbid", null)] public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult) { Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute)); From de2d2921979c3e1cfc58cd256e1e5e70169925d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Dardenne?= <benoit.dardenne@gmail.com> Date: Thu, 9 Dec 2021 16:07:57 +0100 Subject: [PATCH 104/167] Added artist to '/' split whitelist --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 32ff1dee6b..1b3a4fa0f7 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -45,6 +45,7 @@ namespace MediaBrowser.MediaEncoding.Probing { "AC/DC", "Au/Ra", + "Bremer/McCoy", "이달의 소녀 1/3", "LOONA 1/3", "LOONA / yyxy", From eeb8192602627e912b491f3e7e6696ce415f3ce3 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Thu, 9 Dec 2021 08:38:00 -0700 Subject: [PATCH 105/167] Add StringComparison --- Emby.Server.Implementations/Library/PathExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 78850c149e..3fcbb4d65f 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("String can't be empty.", nameof(attribute)); } - var attributeIndex = str.IndexOf(attribute); + var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase); // Must be at least 3 characters after the attribute =, ], any character. var maxIndex = str.Length - attribute.Length - 3; @@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library } str = str[attributeEnd..]; - attributeIndex = str.IndexOf(attribute); + attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase); } // for imdbid we also accept pattern matching From 7d86ef6f227aadf5a5f2992fb0f52ba69e07b3cb Mon Sep 17 00:00:00 2001 From: Marius Luca <marius@dreamer-ge.com> Date: Sun, 28 Nov 2021 11:42:51 +0200 Subject: [PATCH 106/167] - add an option for dropping specific subtitle formats using the DLNA SubtitleProfile --- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 7 ++++++- MediaBrowser.Model/Dlna/StreamBuilder.cs | 4 +++- MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs | 7 ++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 4abe4c5d57..02af2e4353 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -462,6 +462,11 @@ namespace Jellyfin.Api.Helpers private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder, ClaimsPrincipal user) { + if (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Drop) + { + return; + } + var selectedIndex = state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Hls ? (int?)null : state.SubtitleStream.Index; const string Format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\""; diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 322cc367b6..31a6ac05a2 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1220,7 +1220,9 @@ namespace MediaBrowser.Model.Dlna { var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, playMethod, _transcoderSupport, item.Container, null); - if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed) + if (subtitleProfile.Method != SubtitleDeliveryMethod.Drop + && subtitleProfile.Method != SubtitleDeliveryMethod.External + && subtitleProfile.Method != SubtitleDeliveryMethod.Embed) { _logger.LogDebug("Not eligible for {0} due to unsupported subtitles", playMethod); return (false, TranscodeReason.SubtitleCodecNotSupported); diff --git a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs index 9b39f9e11a..69bda2d911 100644 --- a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs +++ b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs @@ -25,6 +25,11 @@ namespace MediaBrowser.Model.Dlna /// <summary> /// Serve the subtitles as a separate HLS stream. /// </summary> - Hls = 3 + Hls = 3, + + /// <summary> + /// Drop the subtitle. + /// </summary> + Drop = 4 } } From d707a201c9568df374579d6dde56d8068a070da0 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Thu, 9 Dec 2021 12:31:32 -0700 Subject: [PATCH 107/167] update tests --- .../Library/PathExtensionsTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index 950f0e76d5..295a3c83e1 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -15,8 +15,8 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "imdbid", "tt10985510")] [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "tmdbid", "618355")] [InlineData("[tmdbid=618355]", "tmdbid", "618355")] - [InlineData("tmdbid=618355][tmdbid=618355]", "tmdbid", "618355")] - [InlineData("[tmdbid=618355]tmdbid=618355]", "tmdbid", "618355")] + [InlineData("tmdbid=111111][tmdbid=618355]", "tmdbid", "618355")] + [InlineData("[tmdbid=618355]tmdbid=111111]", "tmdbid", "618355")] [InlineData("tmdbid=618355]", "tmdbid", null)] [InlineData("[tmdbid=618355", "tmdbid", null)] [InlineData("tmdbid=618355", "tmdbid", null)] From 4d446eb614584b27366d5994bd01552ed4108b70 Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Thu, 9 Dec 2021 06:06:59 +0000 Subject: [PATCH 108/167] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- Emby.Server.Implementations/Localization/Core/eo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index 3cadde0a01..8abf7fa660 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -104,7 +104,7 @@ "TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.", "TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn", "TaskCleanTranscode": "Malplenigi Transkodadan Katalogon", - "TaskRefreshChapterImages": "Eltiri Ĉapitraj Bildojn", + "TaskRefreshChapterImages": "Eltiri Ĉapitrajn Bildojn", "TaskCleanCache": "Malplenigi Staplan Katalogon", "TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon", "PluginUpdatedWithName": "{0} estis ĝisdatigita", From 220443eca1dfef08a2a0e000ac5963e18af073fc Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Fri, 10 Dec 2021 14:23:31 +0100 Subject: [PATCH 109/167] Simplify StackResolver --- Emby.Naming/Common/NamingOptions.cs | 18 +- Emby.Naming/Video/FileStack.cs | 28 +-- Emby.Naming/Video/FileStackRule.cs | 48 +++++ Emby.Naming/Video/StackResolver.cs | 204 +++++------------- .../Common/NamingOptionsTest.cs | 1 - .../Jellyfin.Naming.Tests/Video/StackTests.cs | 7 +- .../Video/VideoListResolverTests.cs | 2 +- 7 files changed, 131 insertions(+), 177 deletions(-) create mode 100644 Emby.Naming/Video/FileStackRule.cs diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 86c79166d1..b97e9d7637 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -124,11 +124,11 @@ namespace Emby.Naming.Common token: "DSR") }; - VideoFileStackingExpressions = new[] + VideoFileStackingRules = new[] { - "^(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|part|pt|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$", - "^(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|part|pt|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$", - "^(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$" + new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true), + new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false), + new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false) }; CleanDateTimes = new[] @@ -765,9 +765,9 @@ namespace Emby.Naming.Common public Format3DRule[] Format3DRules { get; set; } /// <summary> - /// Gets or sets list of raw video file-stacking expressions strings. + /// Gets the file stacking rules. /// </summary> - public string[] VideoFileStackingExpressions { get; set; } + public FileStackRule[] VideoFileStackingRules { get; } /// <summary> /// Gets or sets list of raw clean DateTimes regular expressions strings. @@ -789,11 +789,6 @@ namespace Emby.Naming.Common /// </summary> public ExtraRule[] VideoExtraRules { get; set; } - /// <summary> - /// Gets list of video file-stack regular expressions. - /// </summary> - public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty<Regex>(); - /// <summary> /// Gets list of clean datetime regular expressions. /// </summary> @@ -819,7 +814,6 @@ namespace Emby.Naming.Common /// </summary> public void Compile() { - VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray(); CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray(); CleanStringRegexes = CleanStrings.Select(Compile).ToArray(); EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray(); diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs index a4a4716ca9..bd635a9f78 100644 --- a/Emby.Naming/Video/FileStack.cs +++ b/Emby.Naming/Video/FileStack.cs @@ -12,25 +12,30 @@ namespace Emby.Naming.Video /// <summary> /// Initializes a new instance of the <see cref="FileStack"/> class. /// </summary> - public FileStack() + /// <param name="name">The stack name.</param> + /// <param name="isDirectory">Whether the stack files are directories.</param> + /// <param name="files">The stack files.</param> + public FileStack(string name, bool isDirectory, IReadOnlyList<string> files) { - Files = new List<string>(); + Name = name; + IsDirectoryStack = isDirectory; + Files = files; } /// <summary> - /// Gets or sets name of file stack. + /// Gets the name of file stack. /// </summary> - public string Name { get; set; } = string.Empty; + public string Name { get; } /// <summary> - /// Gets or sets list of paths in stack. + /// Gets the list of paths in stack. /// </summary> - public List<string> Files { get; set; } + public IReadOnlyList<string> Files { get; } /// <summary> - /// Gets or sets a value indicating whether stack is directory stack. + /// Gets a value indicating whether stack is directory stack. /// </summary> - public bool IsDirectoryStack { get; set; } + public bool IsDirectoryStack { get; } /// <summary> /// Helper function to determine if path is in the stack. @@ -45,12 +50,7 @@ namespace Emby.Naming.Video return false; } - if (IsDirectoryStack == isDirectory) - { - return Files.Contains(file, StringComparer.OrdinalIgnoreCase); - } - - return false; + return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparer.OrdinalIgnoreCase); } } } diff --git a/Emby.Naming/Video/FileStackRule.cs b/Emby.Naming/Video/FileStackRule.cs new file mode 100644 index 0000000000..36a765dfb8 --- /dev/null +++ b/Emby.Naming/Video/FileStackRule.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Video; + +/// <summary> +/// Regex based rule for file stacking (eg. disc1, disc2). +/// </summary> +public class FileStackRule +{ + private readonly Regex _tokenRegex; + + /// <summary> + /// Initializes a new instance of the <see cref="FileStackRule"/> class. + /// </summary> + /// <param name="token">Token.</param> + /// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param> + public FileStackRule(string token, bool isNumerical) + { + _tokenRegex = new Regex(token, RegexOptions.IgnoreCase); + IsNumerical = isNumerical; + } + + /// <summary> + /// Gets a value indicating whether the rule uses numerical or alphabetical numbering. + /// </summary> + public bool IsNumerical { get; } + + /// <summary> + /// Match the input against the rule regex. + /// </summary> + /// <param name="input">The input.</param> + /// <param name="result">The part type and number or <c>null</c>.</param> + /// <returns>A value indicating whether the input matched the rule.</returns> + public bool Match(string input, [NotNullWhen(true)] out (string StackName, string PartType, string PartNumber)? result) + { + result = null; + var match = _tokenRegex.Match(input); + if (!match.Success) + { + return false; + } + + var partType = match.Groups["parttype"].Success ? match.Groups["parttype"].Value : "vol"; + result = (match.Groups["filename"].Value, partType, match.Groups["number"].Value); + return true; + } +} diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index be73f69db1..7900e09c2d 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text.RegularExpressions; using Emby.Naming.AudioBook; using Emby.Naming.Common; using MediaBrowser.Model.IO; @@ -51,19 +50,13 @@ namespace Emby.Naming.Video { foreach (var file in directory) { - var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false }; - stack.Files.Add(file.Path); + var stack = new FileStack(Path.GetFileNameWithoutExtension(file.Path), false, new[] { file.Path }); yield return stack; } } else { - var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false }; - foreach (var file in directory) - { - stack.Files.Add(file.Path); - } - + var stack = new FileStack(Path.GetFileName(directory.Key), false, directory.Select(f => f.Path).ToArray()); yield return stack; } } @@ -77,166 +70,87 @@ namespace Emby.Naming.Video /// <returns>Enumerable <see cref="FileStack"/> of videos.</returns> public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions) { - var list = files + var potentialFiles = files .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions)) - .OrderBy(i => i.FullName) - .Select(f => (f.IsDirectory, FileName: GetFileNameWithExtension(f), f.FullName)) - .ToList(); + .OrderBy(i => i.FullName); - // TODO is there a "nicer" way? - var cache = new Dictionary<(string, Regex, int), Match>(); - - var expressions = namingOptions.VideoFileStackingRegexes; - - for (var i = 0; i < list.Count; i++) + var potentialStacks = new Dictionary<string, StackMetadata>(); + foreach (var file in potentialFiles) { - var offset = 0; - - var file1 = list[i]; - - var expressionIndex = 0; - while (expressionIndex < expressions.Length) + for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++) { - var exp = expressions[expressionIndex]; - FileStack? stack = null; - - // (Title)(Volume)(Ignore)(Extension) - var match1 = FindMatch(file1.FileName, exp, offset, cache); - - if (match1.Success) + var name = file.Name; + if (string.IsNullOrEmpty(name)) { - var title1 = match1.Groups[1].Value; - var volume1 = match1.Groups[2].Value; - var ignore1 = match1.Groups[3].Value; - var extension1 = match1.Groups[4].Value; + name = Path.GetFileName(file.FullName); + } - var j = i + 1; - while (j < list.Count) + var rule = namingOptions.VideoFileStackingRules[i]; + if (!rule.Match(name, out var stackParsingResult)) + { + continue; + } + + var stackName = stackParsingResult.Value.StackName; + var partNumber = stackParsingResult.Value.PartNumber; + var partType = stackParsingResult.Value.PartType; + + if (!potentialStacks.TryGetValue(stackName, out var stackResult)) + { + stackResult = new StackMetadata(file.IsDirectory, rule.IsNumerical, partType); + potentialStacks[stackName] = stackResult; + } + + if (stackResult.Parts.Count > 0) + { + if (stackResult.IsDirectory != file.IsDirectory + || !string.Equals(partType, stackResult.PartType, StringComparison.OrdinalIgnoreCase) + || stackResult.ContainsPart(partNumber)) { - var file2 = list[j]; - - if (file1.IsDirectory != file2.IsDirectory) - { - j++; - continue; - } - - // (Title)(Volume)(Ignore)(Extension) - var match2 = FindMatch(file2.FileName, exp, offset, cache); - - if (match2.Success) - { - var title2 = match2.Groups[1].Value; - var volume2 = match2.Groups[2].Value; - var ignore2 = match2.Groups[3].Value; - var extension2 = match2.Groups[4].Value; - - if (string.Equals(title1, title2, StringComparison.OrdinalIgnoreCase)) - { - if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase) - && string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase)) - { - stack ??= new FileStack(); - if (stack.Files.Count == 0) - { - stack.Name = title1 + ignore1; - stack.IsDirectoryStack = file1.IsDirectory; - stack.Files.Add(file1.FullName); - } - - stack.Files.Add(file2.FullName); - } - else - { - // Sequel - offset = 0; - expressionIndex++; - break; - } - } - else if (!string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)) - { - // False positive, try again with offset - offset = match1.Groups[3].Index; - break; - } - else - { - // Extension mismatch - offset = 0; - expressionIndex++; - break; - } - } - else - { - // Title mismatch - offset = 0; - expressionIndex++; - break; - } - } - else - { - // No match 2, next expression - offset = 0; - expressionIndex++; - break; - } - - j++; + continue; } - if (j == list.Count) + if (rule.IsNumerical != stackResult.IsNumerical) { - expressionIndex = expressions.Length; + break; } } - else - { - // No match 1 - offset = 0; - expressionIndex++; - } - if (stack?.Files.Count > 1) - { - yield return stack; - i += stack.Files.Count - 1; - break; - } + stackResult.Parts.Add(partNumber, file); + break; } } + + foreach (var (fileName, stack) in potentialStacks) + { + if (stack.Parts.Count < 2) + { + continue; + } + + yield return new FileStack(fileName, stack.IsDirectory, stack.Parts.Select(kv => kv.Value.FullName).ToArray()); + } } - private static string GetFileNameWithExtension(FileSystemMetadata file) + private class StackMetadata { - // For directories, dummy up an extension otherwise the expressions will fail - var input = file.FullName; - if (file.IsDirectory) + public StackMetadata(bool isDirectory, bool isNumerical, string partType) { - input = Path.ChangeExtension(input, "mkv"); + Parts = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase); + IsDirectory = isDirectory; + IsNumerical = isNumerical; + PartType = partType; } - return Path.GetFileName(input); - } + public Dictionary<string, FileSystemMetadata> Parts { get; } - private static Match FindMatch(string input, Regex regex, int offset, Dictionary<(string, Regex, int), Match> cache) - { - if (offset < 0 || offset >= input.Length) - { - return Match.Empty; - } + public bool IsDirectory { get; } - if (!cache.TryGetValue((input, regex, offset), out var result)) - { - result = regex.Match(input, offset, input.Length - offset); - cache.Add((input, regex, offset), result); - } + public bool IsNumerical { get; } - return result; + public string PartType { get; } + + public bool ContainsPart(string partNumber) => Parts.ContainsKey(partNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs b/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs index 3892d00f61..58aaed023a 100644 --- a/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs +++ b/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs @@ -10,7 +10,6 @@ namespace Jellyfin.Naming.Tests.Common { var options = new NamingOptions(); - Assert.NotEmpty(options.VideoFileStackingRegexes); Assert.NotEmpty(options.CleanDateTimeRegexes); Assert.NotEmpty(options.CleanStringRegexes); Assert.NotEmpty(options.EpisodeWithoutSeasonRegexes); diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs index 41da0e0771..368c3592ef 100644 --- a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs @@ -128,7 +128,7 @@ namespace Jellyfin.Naming.Tests.Video } [Fact] - public void TestDirtyNames() + public void ResolveFiles_GivenPartInMiddleOfName_ReturnsNoStack() { var files = new[] { @@ -141,12 +141,11 @@ namespace Jellyfin.Naming.Tests.Video var result = StackResolver.ResolveFiles(files, _namingOptions).ToList(); - Assert.Single(result); - TestStackInfo(result[0], "Bad Boys (2006).stv.unrated.multi.1080p.bluray.x264-rough", 4); + Assert.Empty(result); } [Fact] - public void TestNumberedFiles() + public void ResolveFiles_FileNamesWithMissingPartType_ReturnsNoStack() { var files = new[] { diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index b171d739a8..cda4967613 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -489,7 +489,7 @@ namespace Jellyfin.Naming.Tests.Video [Fact] public void TestDirectoryStack() { - var stack = new FileStack(); + var stack = new FileStack(string.Empty, false, Array.Empty<string>()); Assert.False(stack.ContainsFile("XX", true)); } } From 681bfbd535b063f6d4e3c61bf0bc250d4eee9304 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Fri, 10 Dec 2021 14:34:46 +0100 Subject: [PATCH 110/167] Remove duplication --- Emby.Naming/Video/ExtraResolver.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index cd7a6c0d7a..8dab084f6b 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -98,7 +98,6 @@ namespace Emby.Naming.Video { var parentDir = videoInfo.IsDirectory ? videoInfo.Path : Path.GetDirectoryName(videoInfo.Path.AsSpan()); - var trimmedFileName = TrimFilenameDelimiters(videoInfo.Name, videoFlagDelimiters); var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(videoInfo.FileNameWithoutExtension, videoFlagDelimiters); var trimmedVideoInfoName = TrimFilenameDelimiters(videoInfo.Name, videoFlagDelimiters); @@ -117,7 +116,6 @@ namespace Emby.Naming.Video // first check filenames bool isValid = StartsWith(trimmedCurrentFileName, trimmedFileNameWithoutExtension) - || (StartsWith(trimmedCurrentFileName, trimmedFileName) && currentFile.Year == videoInfo.Year) || (StartsWith(trimmedCurrentFileName, trimmedVideoInfoName) && currentFile.Year == videoInfo.Year); // then by directory From 6d6fb1bba309f7566a856be7f9b74e7b48f54231 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Sat, 11 Dec 2021 16:02:51 +0100 Subject: [PATCH 111/167] Rename unknown parttype to "unknown" --- Emby.Naming/Video/FileStackRule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/Video/FileStackRule.cs b/Emby.Naming/Video/FileStackRule.cs index 36a765dfb8..76b487f428 100644 --- a/Emby.Naming/Video/FileStackRule.cs +++ b/Emby.Naming/Video/FileStackRule.cs @@ -41,7 +41,7 @@ public class FileStackRule return false; } - var partType = match.Groups["parttype"].Success ? match.Groups["parttype"].Value : "vol"; + var partType = match.Groups["parttype"].Success ? match.Groups["parttype"].Value : "unknown"; result = (match.Groups["filename"].Value, partType, match.Groups["number"].Value); return true; } From 3797879d05ae3c1d17462593c2f3b04f94eccfae Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sat, 11 Dec 2021 14:50:55 -0500 Subject: [PATCH 112/167] Automatically bump stable versions in build Should prevent strangeness with building these for pre-releases or actual release versions. Whatever the Git tag is, becomes the package version. --- .ci/azure-pipelines-package.yml | 20 ++++++++++++++++++++ bump_version | 18 ++++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 81693452f3..89f7137fd0 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -39,6 +39,14 @@ jobs: vmImage: 'ubuntu-latest' steps: + - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )" + displayName: Set release version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + + - script: './bump-version $(JellyfinVersion)' + displayName: Bump internal version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment' displayName: 'Build Dockerfile' @@ -80,6 +88,14 @@ jobs: vmImage: 'ubuntu-latest' steps: + - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )" + displayName: Set release version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + + - script: './bump-version $(JellyfinVersion)' + displayName: Bump internal version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + - task: DownloadPipelineArtifact@2 displayName: 'Download OpenAPI Spec' inputs: @@ -127,6 +143,10 @@ jobs: displayName: Set release version (stable) condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + - script: './bump-version $(JellyfinVersion)' + displayName: Bump internal version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + - task: Docker@2 displayName: 'Push Unstable Image' condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') diff --git a/bump_version b/bump_version index f615606e2d..1fcf7197c4 100755 --- a/bump_version +++ b/bump_version @@ -52,7 +52,8 @@ echo $old_version # Set the build.yaml version to the specified new_version old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars -sed -i "s/${old_version_sed}/${new_version}/g" ${build_file} +new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" +sed -i "s/${old_version_sed}/${new_version_sed}/g" ${build_file} # update nuget package version for subproject in ${jellyfin_subprojects[@]}; do @@ -64,26 +65,27 @@ for subproject in ${jellyfin_subprojects[@]}; do | sed -E 's/<VersionPrefix>([0-9\.]+[-a-z0-9]*)<\/VersionPrefix>/\1/' )" echo old nuget version: $old_version + new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" # Set the nuget version to the specified new_version - sed -i "s|${old_version}|${new_version}|g" ${subproject} + sed -i "s|${old_version}|${new_version_sed}|g" ${subproject} done if [[ ${new_version} == *"-"* ]]; then - new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )" + new_version_pkg="$( sed 's/-/~/g' <<<"${new_version}" )" else - new_version_deb="${new_version}-1" + new_version_pkg="${new_version}-1" fi # Update the metapackage equivs file debian_equivs_file="debian/metapackage/jellyfin" -sed -i "s/${old_version_sed}/${new_version}/g" ${debian_equivs_file} +sed -i "s/${old_version_sed}/${new_version_pkg}/g" ${debian_equivs_file} # Write out a temporary Debian changelog with our new stuff appended and some templated formatting debian_changelog_file="debian/changelog" debian_changelog_temp="$( mktemp )" # Create new temp file with our changelog -echo -e "jellyfin-server (${new_version_deb}) unstable; urgency=medium +echo -e "jellyfin-server (${new_version_pkg}) unstable; urgency=medium * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version} @@ -104,7 +106,7 @@ pushd ${fedora_spec_temp_dir} # Split out the stuff before and after changelog csplit jellyfin.spec "/^%changelog/" # produces xx00 xx01 # Update the version in xx00 -sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00 +sed -i "s/${old_version_sed}/${new_version_pkg}/g" xx00 # Remove the header from xx01 sed -i '/^%changelog/d' xx01 # Create new temp file with our changelog @@ -121,5 +123,5 @@ mv ${fedora_spec_temp} ${fedora_spec_file} rm -rf ${fedora_spec_temp_dir} # Stage the changed files for commit -git add ${shared_version_file} ${build_file} ${debian_equivs_file} ${debian_changelog_file} ${fedora_spec_file} +git add . git status From 3223ef0e49030ec5470cb73c85ba8f978946a49b Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sat, 11 Dec 2021 14:58:20 -0500 Subject: [PATCH 113/167] Properly handle the -1 tag Avoids breaking Fedora/CentOS while preserving Debian. --- bump_version | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bump_version b/bump_version index 1fcf7197c4..3c5cb7897b 100755 --- a/bump_version +++ b/bump_version @@ -73,8 +73,10 @@ done if [[ ${new_version} == *"-"* ]]; then new_version_pkg="$( sed 's/-/~/g' <<<"${new_version}" )" + new_version_deb_sup="" else - new_version_pkg="${new_version}-1" + new_version_pkg="${new_version}" + new_version_deb_sup="-1" fi # Update the metapackage equivs file @@ -85,7 +87,7 @@ sed -i "s/${old_version_sed}/${new_version_pkg}/g" ${debian_equivs_file} debian_changelog_file="debian/changelog" debian_changelog_temp="$( mktemp )" # Create new temp file with our changelog -echo -e "jellyfin-server (${new_version_pkg}) unstable; urgency=medium +echo -e "jellyfin-server (${new_version_pkg}${new_version_deb_sup}) unstable; urgency=medium * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version} From 584cf47ef5faecf3c951bb0b767c404b94157c95 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sat, 11 Dec 2021 15:03:07 -0500 Subject: [PATCH 114/167] Show detailed git status --- bump_version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bump_version b/bump_version index 3c5cb7897b..41d27f5c8a 100755 --- a/bump_version +++ b/bump_version @@ -126,4 +126,4 @@ rm -rf ${fedora_spec_temp_dir} # Stage the changed files for commit git add . -git status +git status -v From 1f02ab83cb592be38aaa0c2e24ac6506c447cf4e Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Fri, 10 Dec 2021 20:39:57 +0000 Subject: [PATCH 115/167] Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/ --- Emby.Server.Implementations/Localization/Core/fa.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 3d3b3533fc..6960ff007a 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -118,5 +118,6 @@ "Default": "پیشفرض", "TaskCleanActivityLogDescription": "ورودی‌های قدیمی‌تر از سن تنظیم شده در سیاهه فعالیت را حذف می‌کند.", "TaskCleanActivityLog": "پاکسازی سیاهه فعالیت", - "Undefined": "تعریف نشده" + "Undefined": "تعریف نشده", + "TaskOptimizeDatabase": "بهینه سازی پایگاه داده" } From 5e8f27d473af677b2669ca29a22357a0ec712fb9 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke <inoue.daisuke@gmail.com> Date: Fri, 10 Dec 2021 13:47:38 +0000 Subject: [PATCH 116/167] Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/ --- Emby.Server.Implementations/Localization/Core/ja.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index 7f41561ecf..2588f1e8c0 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -16,7 +16,7 @@ "Folders": "フォルダー", "Genres": "ジャンル", "HeaderAlbumArtists": "アルバムアーティスト", - "HeaderContinueWatching": "視聴を続ける", + "HeaderContinueWatching": "続きを見る", "HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteArtists": "お気に入りのアーティスト", "HeaderFavoriteEpisodes": "お気に入りのエピソード", From a90614d194314f8a4d6f097637836610ce8b6bbe Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Fri, 10 Dec 2021 17:45:25 +0000 Subject: [PATCH 117/167] Translated using Weblate (Zulu) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zu/ --- .../Localization/Core/zu.json | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zu.json b/Emby.Server.Implementations/Localization/Core/zu.json index 0967ef424b..b5f4b920f3 100644 --- a/Emby.Server.Implementations/Localization/Core/zu.json +++ b/Emby.Server.Implementations/Localization/Core/zu.json @@ -1 +1,29 @@ -{} +{ + "TasksApplicationCategory": "Ukusetshenziswa", + "TasksLibraryCategory": "Umtapo", + "TasksMaintenanceCategory": "Ukunakekela", + "User": "Umsebenzisi", + "Undefined": "Akuchaziwe", + "System": "Isistimu", + "Sync": "Vumelanisa", + "Songs": "Amaculo", + "Shows": "Izinhlelo", + "Plugin": "Isijobelelo", + "Playlists": "Izinhla Zokudlalayo", + "Photos": "Izithombe", + "Music": "Umculo", + "Movies": "Amamuvi", + "Latest": "lwakamuva", + "Inherit": "Ngefa", + "Forced": "Kuphoqiwe", + "Application": "Ukusetshenziswa", + "Genres": "Izinhlobo", + "Folders": "Izikhwama", + "Favorites": "Izintandokazi", + "Default": "Okumisiwe", + "Collections": "Amaqoqo", + "Channels": "Amashaneli", + "Books": "Izincwadi", + "Artists": "Abadlali", + "Albums": "Ama-albhamu" +} From 510f92f4c5b8a4ac969c0761bf54dbd742b05a68 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Sun, 12 Dec 2021 01:26:47 +0100 Subject: [PATCH 118/167] Don't check floats for equality --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index bf6146e2b1..770881149f 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.MediaEncoding.Probing { "AC/DC", "Au/Ra", - "Bremer/McCoy", + "Bremer/McCoy", "이달의 소녀 1/3", "LOONA 1/3", "LOONA / yyxy", @@ -723,8 +723,8 @@ namespace MediaBrowser.MediaEncoding.Probing // Some interlaced H.264 files in mp4 containers using MBAFF coding aren't flagged as being interlaced by FFprobe, // so for H.264 files we also calculate the frame rate from the codec time base and check if it is double the reported // frame rate (both rounded to the nearest integer) to determine if the file is interlaced - float roundedTimeBaseFPS = MathF.Round(1 / GetFrameRate(stream.CodecTimeBase) ?? 0); - float roundedDoubleFrameRate = MathF.Round(stream.AverageFrameRate * 2 ?? 0); + int roundedTimeBaseFPS = Convert.ToInt32(1 / GetFrameRate(stream.CodecTimeBase) ?? 0); + int roundedDoubleFrameRate = Convert.ToInt32(stream.AverageFrameRate * 2 ?? 0); bool videoInterlaced = !string.IsNullOrWhiteSpace(streamInfo.FieldOrder) && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase); From 7ee96a59d3b69e15aec1df33ad46f47021f8a20b Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Sun, 12 Dec 2021 02:07:35 +0100 Subject: [PATCH 119/167] Use correct jpeg MIME type image/jpg isn't a valid MIME type --- MediaBrowser.Model/Net/MimeTypes.cs | 4 ++-- tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 506e8e9d63..3b03466e9d 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -116,7 +116,7 @@ namespace MediaBrowser.Model.Net { "audio/x-wavpack", ".wv" }, // Type image - { "image/jpg", ".jpg" }, + { "image/jpeg", ".jpg" }, { "image/x-png", ".png" }, // Type text @@ -137,7 +137,7 @@ namespace MediaBrowser.Model.Net /// <param name="filename">The filename to find the MIME type of.</param> /// <param name="defaultValue">The default value to return if no fitting MIME type is found.</param> /// <returns>The correct MIME type for the given filename, or <paramref name="defaultValue"/> if it wasn't found.</returns> - [return: NotNullIfNotNullAttribute("defaultValue")] + [return: NotNullIfNotNull("defaultValue")] public static string? GetMimeType(string filename, string? defaultValue = null) { if (filename.Length == 0) diff --git a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs index 55050cc954..cbab455f01 100644 --- a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs +++ b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using MediaBrowser.Model.Net; using Xunit; @@ -129,7 +124,7 @@ namespace Jellyfin.Model.Tests.Net [InlineData("font/woff2", ".woff2")] [InlineData("image/bmp", ".bmp")] [InlineData("image/gif", ".gif")] - [InlineData("image/jpg", ".jpg")] + [InlineData("image/jpeg", ".jpg")] [InlineData("image/png", ".png")] [InlineData("image/svg+xml", ".svg")] [InlineData("image/tiff", ".tif")] From 58f788e8abfce620db5a9b70165ff6cbe0931a46 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Sat, 11 Dec 2021 19:43:01 -0700 Subject: [PATCH 120/167] Update tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs Co-authored-by: Bond-009 <bond.009@outlook.com> --- .../Library/PathExtensionsTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index 295a3c83e1..54a63a5f2f 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -22,6 +22,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("tmdbid=618355", "tmdbid", null)] [InlineData("tmdbid=", "tmdbid", null)] [InlineData("tmdbid", "tmdbid", null)] + [InlineData("[tmdbid=][imdbid=tt10985510]", "tmdbid", null)] public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult) { Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute)); From a04f8f5efb3f067de2ce27e8804aab5c950ac284 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Sat, 11 Dec 2021 20:35:43 -0700 Subject: [PATCH 121/167] Fix new test --- Emby.Server.Implementations/Library/PathExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 3fcbb4d65f..6f61dc7135 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -40,7 +40,8 @@ namespace Emby.Server.Implementations.Library && str[attributeEnd] == '=') { var closingIndex = str[attributeEnd..].IndexOf(']'); - if (closingIndex != -1) + // Must be at least 1 character before the closing bracket. + if (closingIndex > 1) { return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString(); } From 296a61cbc43d1c42c80ceef73bb92f3014f98f03 Mon Sep 17 00:00:00 2001 From: "Brian J. Murrell" <brian@interlinx.bc.ca> Date: Sat, 11 Dec 2021 19:26:00 -0500 Subject: [PATCH 122/167] Run bump_version in make srpm Also add an "rpms" target that builds the RPMs using mock in a target environment. Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca> --- fedora/Makefile | 59 +++++++++++++++++++++++++++----------------- fedora/jellyfin.spec | 2 +- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/fedora/Makefile b/fedora/Makefile index 97904ddd35..6b09458b54 100644 --- a/fedora/Makefile +++ b/fedora/Makefile @@ -1,26 +1,41 @@ VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin.spec) +outdir ?= fedora/ +TARGET ?= fedora-35-x86_64 srpm: - cd fedora/; \ - SOURCE_DIR=.. \ - WORKDIR="$${PWD}"; \ - tar \ - --transform "s,^\.,jellyfin-server-$(VERSION)," \ - --exclude='.git*' \ - --exclude='**/.git' \ - --exclude='**/.hg' \ - --exclude='**/.vs' \ - --exclude='**/.vscode' \ - --exclude='deployment' \ - --exclude='**/bin' \ - --exclude='**/obj' \ - --exclude='**/.nuget' \ - --exclude='*.deb' \ - --exclude='*.rpm' \ - --exclude='jellyfin-server-$(VERSION).tar.gz' \ - -czf "jellyfin-server-$(VERSION).tar.gz" \ - -C $${SOURCE_DIR} ./ - cd fedora/; \ - rpmbuild -bs jellyfin.spec \ - --define "_sourcedir $$PWD/" \ + pushd fedora/; \ + if [ "$$(id -u)" = "0" ]; then \ + dnf -y install git; \ + fi; \ + version=$$(git describe --tags | sed -e 's/^v//' \ + -e 's/-[0-9]*-g.*$$//' \ + -e 's/-/~/'); \ + SOURCE_DIR=.. \ + WORKDIR="$${PWD}"; \ + tar \ + --transform "s,^\.,jellyfin-server-$$version," \ + --exclude='.git*' \ + --exclude='**/.git' \ + --exclude='**/.hg' \ + --exclude='**/.vs' \ + --exclude='**/.vscode' \ + --exclude=deployment \ + --exclude='**/bin' \ + --exclude='**/obj' \ + --exclude='**/.nuget' \ + --exclude='*.deb' \ + --exclude='*.rpm' \ + --exclude=jellyfin-server-$$version.tar.gz \ + -czf "jellyfin-server-$$version.tar.gz" \ + -C $${SOURCE_DIR} ./; \ + popd; \ + ./bump_version $$version + cd fedora/; \ + rpmbuild -bs jellyfin.spec \ + --define "_sourcedir $$PWD/" \ --define "_srcrpmdir $(outdir)" + +rpms: fedora/jellyfin-$(shell git describe --tags | sed -e 's/^v//' -e 's/-[0-9]*-g.*$$//' -e 's/-/~/')-1$(shell rpm --eval %dist).src.rpm + mock --addrepo=https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/ \ + --enable-network \ + -r $(TARGET) $< diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 47dee7c13f..acc6100f63 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -12,7 +12,7 @@ Release: 1%{?dist} Summary: The Free Software Media System License: GPLv3 URL: https://jellyfin.org -# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz` +# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz` Source0: jellyfin-server-%{version}.tar.gz Source11: jellyfin.service Source12: jellyfin.env From 32629cd7da0a39962009bffd9389a660c196f541 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Sat, 11 Dec 2021 19:31:30 -0700 Subject: [PATCH 123/167] Use BaseItemKind where possible --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 46 +++--- .../Channels/ChannelManager.cs | 2 +- .../Channels/ChannelPostScanTask.cs | 3 +- .../Data/SqliteItemRepository.cs | 139 ++++++++---------- Emby.Server.Implementations/Dto/DtoService.cs | 2 +- .../Images/CollectionFolderImageProvider.cs | 16 +- .../Images/DynamicImageProvider.cs | 3 +- .../Images/GenreImageProvider.cs | 2 +- .../Images/MusicGenreImageProvider.cs | 6 +- .../Library/LibraryManager.cs | 2 +- .../Library/MusicManager.cs | 4 +- .../Library/SearchEngine.cs | 46 +++--- .../Library/UserViewManager.cs | 16 +- .../Library/Validators/ArtistsValidator.cs | 3 +- .../Validators/CollectionPostScanTask.cs | 4 +- .../Library/Validators/PeopleValidator.cs | 3 +- .../Library/Validators/StudiosValidator.cs | 3 +- .../LiveTv/EmbyTV/EmbyTV.cs | 14 +- .../LiveTv/LiveTvDtoService.cs | 11 +- .../LiveTv/LiveTvManager.cs | 28 ++-- .../Playlists/PlaylistsFolder.cs | 3 +- .../Session/SessionManager.cs | 2 +- .../TV/TVSeriesManager.cs | 8 +- Jellyfin.Api/Controllers/ArtistsController.cs | 8 +- Jellyfin.Api/Controllers/FilterController.cs | 4 +- Jellyfin.Api/Controllers/GenresController.cs | 14 +- Jellyfin.Api/Controllers/ItemsController.cs | 12 +- Jellyfin.Api/Controllers/LibraryController.cs | 37 ++--- Jellyfin.Api/Controllers/MoviesController.cs | 26 ++-- .../Controllers/MusicGenresController.cs | 14 +- Jellyfin.Api/Controllers/SearchController.cs | 4 +- Jellyfin.Api/Controllers/SessionController.cs | 2 +- Jellyfin.Api/Controllers/StudiosController.cs | 4 +- .../Controllers/SuggestionsController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 2 +- .../Controllers/UserLibraryController.cs | 2 +- Jellyfin.Api/Controllers/YearsController.cs | 4 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 16 -- .../Entities/Audio/MusicArtist.cs | 2 +- .../Entities/Audio/MusicGenre.cs | 3 +- MediaBrowser.Controller/Entities/Folder.cs | 6 +- MediaBrowser.Controller/Entities/Genre.cs | 10 +- .../Entities/InternalItemsQuery.cs | 12 +- MediaBrowser.Controller/Entities/TV/Series.cs | 12 +- .../Entities/UserViewBuilder.cs | 40 ++--- MediaBrowser.Controller/Playlists/Playlist.cs | 4 +- .../Querying/LatestItemsQuery.cs | 3 +- MediaBrowser.Model/Search/SearchQuery.cs | 9 +- MediaBrowser.Model/Session/BrowseRequest.cs | 5 +- .../Manager/ProviderManager.cs | 3 +- .../MediaInfo/SubtitleScheduledTask.cs | 3 +- .../Helpers/RequestHelpersTests.cs | 30 ---- 52 files changed, 305 insertions(+), 354 deletions(-) diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 26a816107a..b354421eaa 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -539,7 +539,7 @@ namespace Emby.Dlna.ContentDirectory User = user, Recursive = true, IsMissing = false, - ExcludeItemTypes = new[] { nameof(Book) }, + ExcludeItemTypes = new[] { BaseItemKind.Book }, IsFolder = isFolder, MediaTypes = mediaTypes, DtoOptions = GetDtoOptions() @@ -619,7 +619,7 @@ namespace Emby.Dlna.ContentDirectory Limit = limit, StartIndex = startIndex, IsVirtualItem = false, - ExcludeItemTypes = new[] { nameof(Book) }, + ExcludeItemTypes = new[] { BaseItemKind.Book }, IsPlaceHolder = false, DtoOptions = GetDtoOptions(), OrderBy = GetOrderBy(sort, folder.IsPreSorted) @@ -644,7 +644,7 @@ namespace Emby.Dlna.ContentDirectory { StartIndex = startIndex, Limit = limit, - IncludeItemTypes = new[] { nameof(LiveTvChannel) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel }, OrderBy = GetOrderBy(sort, false) }; @@ -675,23 +675,23 @@ namespace Emby.Dlna.ContentDirectory switch (stubType) { case StubType.Latest: - return GetLatest(item, query, nameof(Audio)); + return GetLatest(item, query, BaseItemKind.Audio); case StubType.Playlists: return GetMusicPlaylists(query); case StubType.Albums: - return GetChildrenOfItem(item, query, nameof(MusicAlbum)); + return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum); case StubType.Artists: return GetMusicArtists(item, query); case StubType.AlbumArtists: return GetMusicAlbumArtists(item, query); case StubType.FavoriteAlbums: - return GetChildrenOfItem(item, query, nameof(MusicAlbum), true); + return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum, true); case StubType.FavoriteArtists: return GetFavoriteArtists(item, query); case StubType.FavoriteSongs: - return GetChildrenOfItem(item, query, nameof(Audio), true); + return GetChildrenOfItem(item, query, BaseItemKind.Audio, true); case StubType.Songs: - return GetChildrenOfItem(item, query, nameof(Audio)); + return GetChildrenOfItem(item, query, BaseItemKind.Audio); case StubType.Genres: return GetMusicGenres(item, query); } @@ -746,13 +746,13 @@ namespace Emby.Dlna.ContentDirectory case StubType.ContinueWatching: return GetMovieContinueWatching(item, query); case StubType.Latest: - return GetLatest(item, query, nameof(Movie)); + return GetLatest(item, query, BaseItemKind.Movie); case StubType.Movies: - return GetChildrenOfItem(item, query, nameof(Movie)); + return GetChildrenOfItem(item, query, BaseItemKind.Movie); case StubType.Collections: return GetMovieCollections(query); case StubType.Favorites: - return GetChildrenOfItem(item, query, nameof(Movie), true); + return GetChildrenOfItem(item, query, BaseItemKind.Movie, true); case StubType.Genres: return GetGenres(item, query); } @@ -831,13 +831,13 @@ namespace Emby.Dlna.ContentDirectory case StubType.NextUp: return GetNextUp(item, query); case StubType.Latest: - return GetLatest(item, query, nameof(Episode)); + return GetLatest(item, query, BaseItemKind.Episode); case StubType.Series: - return GetChildrenOfItem(item, query, nameof(Series)); + return GetChildrenOfItem(item, query, BaseItemKind.Series); case StubType.FavoriteSeries: - return GetChildrenOfItem(item, query, nameof(Series), true); + return GetChildrenOfItem(item, query, BaseItemKind.Series, true); case StubType.FavoriteEpisodes: - return GetChildrenOfItem(item, query, nameof(Episode), true); + return GetChildrenOfItem(item, query, BaseItemKind.Episode, true); case StubType.Genres: return GetGenres(item, query); } @@ -898,7 +898,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query) { query.Recursive = true; - query.IncludeItemTypes = new[] { nameof(BoxSet) }; + query.IncludeItemTypes = new[] { BaseItemKind.BoxSet }; var result = _libraryManager.GetItemsResult(query); @@ -913,7 +913,7 @@ namespace Emby.Dlna.ContentDirectory /// <param name="itemType">The item type.</param> /// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param> /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns> - private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, string itemType, bool isFavorite = false) + private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType, bool isFavorite = false) { query.Recursive = true; query.Parent = parent; @@ -1013,7 +1013,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query) { query.Parent = null; - query.IncludeItemTypes = new[] { nameof(Playlist) }; + query.IncludeItemTypes = new[] { BaseItemKind.Playlist }; query.Recursive = true; var result = _libraryManager.GetItemsResult(query); @@ -1052,7 +1052,7 @@ namespace Emby.Dlna.ContentDirectory /// <param name="query">The <see cref="InternalItemsQuery"/>.</param> /// <param name="itemType">The item type.</param> /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns> - private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, string itemType) + private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1086,7 +1086,7 @@ namespace Emby.Dlna.ContentDirectory { Recursive = true, ArtistIds = new[] { item.Id }, - IncludeItemTypes = new[] { nameof(MusicAlbum) }, + IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions(), @@ -1115,8 +1115,8 @@ namespace Emby.Dlna.ContentDirectory GenreIds = new[] { item.Id }, IncludeItemTypes = new[] { - nameof(Movie), - nameof(Series) + BaseItemKind.Movie, + BaseItemKind.Series }, Limit = limit, StartIndex = startIndex, @@ -1144,7 +1144,7 @@ namespace Emby.Dlna.ContentDirectory { Recursive = true, GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] { nameof(MusicAlbum) }, + IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions(), diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index f65eaec1c8..8c167824ee 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -541,7 +541,7 @@ namespace Emby.Server.Implementations.Channels return _libraryManager.GetItemIds( new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Channel) }, + IncludeItemTypes = new[] { BaseItemKind.Channel }, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } }).Select(i => GetChannelFeatures(i)).ToArray(); } diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index 2391eed428..b358ba4d55 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -51,7 +52,7 @@ namespace Emby.Server.Implementations.Channels var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Channel) }, + IncludeItemTypes = new[] { BaseItemKind.Channel }, ExcludeItemIds = installedChannelIds.ToArray() }); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 13f1df7c86..7731eb694f 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -196,57 +196,56 @@ namespace Emby.Server.Implementations.Data private static readonly string _mediaAttachmentInsertPrefix; - private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<BaseItemKind> _programTypes = new() { - "Program", - "TvChannel", - "LiveTvProgram", - "LiveTvTvChannel" + BaseItemKind.Program, + BaseItemKind.TvChannel, + BaseItemKind.LiveTvProgram, + BaseItemKind.LiveTvChannel }; - private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<BaseItemKind> _programExcludeParentTypes = new() { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" + BaseItemKind.Series, + BaseItemKind.Season, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicArtist, + BaseItemKind.PhotoAlbum }; - private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<BaseItemKind> _serviceTypes = new() { - "TvChannel", - "LiveTvTvChannel" + BaseItemKind.TvChannel, + BaseItemKind.LiveTvChannel }; - private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<BaseItemKind> _startDateTypes = new() { - "Program", - "LiveTvProgram" + BaseItemKind.Program, + BaseItemKind.LiveTvProgram }; - private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<BaseItemKind> _seriesTypes = new() { - "Book", - "AudioBook", - "Episode", - "Season" + BaseItemKind.Book, + BaseItemKind.AudioBook, + BaseItemKind.Episode, + BaseItemKind.Season }; - private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<BaseItemKind> _artistExcludeParentTypes = new() { - "Series", - "Season", - "PhotoAlbum" + BaseItemKind.Series, + BaseItemKind.Season, + BaseItemKind.PhotoAlbum }; - private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<BaseItemKind> _artistsTypes = new() { - "Audio", - "MusicAlbum", - "MusicVideo", - "AudioBook", - "AudioPodcast" + BaseItemKind.Audio, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicVideo, + BaseItemKind.AudioBook }; private static readonly Type[] _knownTypes = @@ -2212,7 +2211,7 @@ namespace Emby.Server.Implementations.Data private bool HasProgramAttributes(InternalItemsQuery query) { - if (_programExcludeParentTypes.Contains(query.ParentType)) + if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value)) { return false; } @@ -2227,7 +2226,7 @@ namespace Emby.Server.Implementations.Data private bool HasServiceName(InternalItemsQuery query) { - if (_programExcludeParentTypes.Contains(query.ParentType)) + if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value)) { return false; } @@ -2242,7 +2241,7 @@ namespace Emby.Server.Implementations.Data private bool HasStartDate(InternalItemsQuery query) { - if (_programExcludeParentTypes.Contains(query.ParentType)) + if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value)) { return false; } @@ -2262,7 +2261,7 @@ namespace Emby.Server.Implementations.Data return true; } - return query.IncludeItemTypes.Contains("Episode", StringComparer.OrdinalIgnoreCase); + return query.IncludeItemTypes.Contains(BaseItemKind.Episode); } private bool HasTrailerTypes(InternalItemsQuery query) @@ -2272,12 +2271,12 @@ namespace Emby.Server.Implementations.Data return true; } - return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase); + return query.IncludeItemTypes.Contains(BaseItemKind.Trailer); } private bool HasArtistFields(InternalItemsQuery query) { - if (_artistExcludeParentTypes.Contains(query.ParentType)) + if (query.ParentType != null && _artistExcludeParentTypes.Contains(query.ParentType.Value)) { return false; } @@ -2292,7 +2291,7 @@ namespace Emby.Server.Implementations.Data private bool HasSeriesFields(InternalItemsQuery query) { - if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase)) + if (query.ParentType == BaseItemKind.PhotoAlbum) { return false; } @@ -3487,8 +3486,8 @@ namespace Emby.Server.Implementations.Data if (query.IsMovie == true) { if (query.IncludeItemTypes.Length == 0 - || query.IncludeItemTypes.Contains(nameof(Movie)) - || query.IncludeItemTypes.Contains(nameof(Trailer))) + || query.IncludeItemTypes.Contains(BaseItemKind.Movie) + || query.IncludeItemTypes.Contains(BaseItemKind.Trailer)) { whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)"); } @@ -3563,15 +3562,15 @@ namespace Emby.Server.Implementations.Data statement?.TryBind("@IsFolder", query.IsFolder); } - var includeTypes = query.IncludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray(); + var includeTypes = query.IncludeItemTypes; // Only specify excluded types if no included types are specified - if (includeTypes.Length == 0) + if (query.IncludeItemTypes.Length == 0) { - var excludeTypes = query.ExcludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray(); + var excludeTypes = query.ExcludeItemTypes; if (excludeTypes.Length == 1) { whereClauses.Add("type<>@type"); - statement?.TryBind("@type", excludeTypes[0]); + statement?.TryBind("@type", excludeTypes[0].ToString()); } else if (excludeTypes.Length > 1) { @@ -3582,7 +3581,7 @@ namespace Emby.Server.Implementations.Data else if (includeTypes.Length == 1) { whereClauses.Add("type=@type"); - statement?.TryBind("@type", includeTypes[0]); + statement?.TryBind("@type", includeTypes[0].ToString()); } else if (includeTypes.Length > 1) { @@ -3911,7 +3910,7 @@ namespace Emby.Server.Implementations.Data if (query.IsPlayed.HasValue) { // We should probably figure this out for all folders, but for right now, this is the only place where we need it - if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(Series), StringComparison.OrdinalIgnoreCase)) + if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.Series) { if (query.IsPlayed.Value) { @@ -4761,27 +4760,27 @@ namespace Emby.Server.Implementations.Data { var list = new List<string>(); - if (IsTypeInQuery(nameof(Person), query)) + if (IsTypeInQuery(BaseItemKind.Person, query)) { list.Add(typeof(Person).FullName); } - if (IsTypeInQuery(nameof(Genre), query)) + if (IsTypeInQuery(BaseItemKind.Genre, query)) { list.Add(typeof(Genre).FullName); } - if (IsTypeInQuery(nameof(MusicGenre), query)) + if (IsTypeInQuery(BaseItemKind.MusicGenre, query)) { list.Add(typeof(MusicGenre).FullName); } - if (IsTypeInQuery(nameof(MusicArtist), query)) + if (IsTypeInQuery(BaseItemKind.MusicArtist, query)) { list.Add(typeof(MusicArtist).FullName); } - if (IsTypeInQuery(nameof(Studio), query)) + if (IsTypeInQuery(BaseItemKind.Studio, query)) { list.Add(typeof(Studio).FullName); } @@ -4789,14 +4788,14 @@ namespace Emby.Server.Implementations.Data return list; } - private bool IsTypeInQuery(string type, InternalItemsQuery query) + private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query) { - if (query.ExcludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) + if (query.ExcludeItemTypes.Contains(type)) { return false; } - return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase); + return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type); } private string GetCleanValue(string value) @@ -4836,12 +4835,12 @@ namespace Emby.Server.Implementations.Data return true; } - if (query.IncludeItemTypes.Contains(nameof(Episode), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(Video), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(Movie), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(MusicVideo), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(Series), StringComparer.OrdinalIgnoreCase) - || query.IncludeItemTypes.Contains(nameof(Season), StringComparer.OrdinalIgnoreCase)) + if (query.IncludeItemTypes.Contains(BaseItemKind.Episode) + || query.IncludeItemTypes.Contains(BaseItemKind.Video) + || query.IncludeItemTypes.Contains(BaseItemKind.Movie) + || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo) + || query.IncludeItemTypes.Contains(BaseItemKind.Series) + || query.IncludeItemTypes.Contains(BaseItemKind.Season)) { return true; } @@ -4890,22 +4889,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return dict; } - private string MapIncludeItemTypes(string value) - { - if (_types.TryGetValue(value, out string result)) - { - return result; - } - - if (IsValidType(value)) - { - return value; - } - - Logger.LogWarning("Unknown item type: {ItemType}", value); - return null; - } - public void DeleteItem(Guid id) { if (id == Guid.Empty) @@ -5569,7 +5552,7 @@ AND Type = @InternalPersonType)"); return result; } - private static ItemCounts GetItemCounts(IReadOnlyList<ResultSetValue> reader, int countStartColumn, string[] typesToCount) + private static ItemCounts GetItemCounts(IReadOnlyList<ResultSetValue> reader, int countStartColumn, BaseItemKind[] typesToCount) { var counts = new ItemCounts(); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index b91ff64087..a34bfdb750 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -470,7 +470,7 @@ namespace Emby.Server.Implementations.Dto { var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(MusicAlbum) }, + IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Name = item.Album, Limit = 1 }); diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 0229fbae79..7e12ebb087 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -28,35 +28,35 @@ namespace Emby.Server.Implementations.Images var view = (CollectionFolder)item; var viewType = view.CollectionType; - string[] includeItemTypes; + BaseItemKind[] includeItemTypes; if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "Movie" }; + includeItemTypes = new[] { BaseItemKind.Movie }; } else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "Series" }; + includeItemTypes = new[] { BaseItemKind.Series }; } else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "MusicAlbum" }; + includeItemTypes = new[] { BaseItemKind.MusicAlbum }; } else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "Book", "AudioBook" }; + includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook }; } else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "BoxSet" }; + includeItemTypes = new[] { BaseItemKind.BoxSet }; } else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal)) { - includeItemTypes = new string[] { "Video", "Photo" }; + includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo }; } else { - includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" }; + includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series }; } var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase); diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 900b3fd9c6..0c3fe33a38 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -41,7 +42,7 @@ namespace Emby.Server.Implementations.Images User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null, CollapseBoxSetItems = false, Recursive = recursive, - ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" }, + ExcludeItemTypes = new[] { BaseItemKind.UserView, BaseItemKind.CollectionFolder, BaseItemKind.Person }, DtoOptions = new DtoOptions(false) }); diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index 1f5090f7f5..f8eefad6b5 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.Images return _libraryManager.GetItemList(new InternalItemsQuery { Genres = new[] { item.Name }, - IncludeItemTypes = new[] { nameof(Series), nameof(Movie) }, + IncludeItemTypes = new[] { BaseItemKind.Series, BaseItemKind.Movie }, OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, diff --git a/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs b/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs index baf1c90517..31f053f065 100644 --- a/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs @@ -44,9 +44,9 @@ namespace Emby.Server.Implementations.Images Genres = new[] { item.Name }, IncludeItemTypes = new[] { - nameof(MusicAlbum), - nameof(MusicVideo), - nameof(Audio) + BaseItemKind.MusicAlbum, + BaseItemKind.MusicVideo, + BaseItemKind.Audio }, OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 778b6225e1..57f66dcc30 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -965,7 +965,7 @@ namespace Emby.Server.Implementations.Library { var existing = GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(MusicArtist) }, + IncludeItemTypes = new[] { BaseItemKind.MusicArtist }, Name = name, DtoOptions = options }).Cast<MusicArtist>() diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index e2f1fb0ade..d332135647 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library var genres = item .GetRecursiveChildren(user, new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(Audio) }, + IncludeItemTypes = new[] { BaseItemKind.Audio }, DtoOptions = dtoOptions }) .Cast<Audio>() @@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library { return _libraryManager.GetItemList(new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(Audio) }, + IncludeItemTypes = new[] { BaseItemKind.Audio }, GenreIds = genreIds.ToArray(), diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index bebc25b77d..42374b2a2b 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -59,9 +59,9 @@ namespace Emby.Server.Implementations.Library }; } - private static void AddIfMissing(List<string> list, string value) + private static void AddIfMissing(List<BaseItemKind> list, BaseItemKind value) { - if (!list.Contains(value, StringComparer.OrdinalIgnoreCase)) + if (!list.Contains(value)) { list.Add(value); } @@ -86,63 +86,63 @@ namespace Emby.Server.Implementations.Library searchTerm = searchTerm.Trim().RemoveDiacritics(); var excludeItemTypes = query.ExcludeItemTypes.ToList(); - var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<string>()).ToList(); + var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<BaseItemKind>()).ToList(); - excludeItemTypes.Add(nameof(Year)); - excludeItemTypes.Add(nameof(Folder)); + excludeItemTypes.Add(BaseItemKind.Year); + excludeItemTypes.Add(BaseItemKind.Folder); - if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase))) + if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Genre))) { if (!query.IncludeMedia) { - AddIfMissing(includeItemTypes, nameof(Genre)); - AddIfMissing(includeItemTypes, nameof(MusicGenre)); + AddIfMissing(includeItemTypes, BaseItemKind.Genre); + AddIfMissing(includeItemTypes, BaseItemKind.MusicGenre); } } else { - AddIfMissing(excludeItemTypes, nameof(Genre)); - AddIfMissing(excludeItemTypes, nameof(MusicGenre)); + AddIfMissing(excludeItemTypes, BaseItemKind.Genre); + AddIfMissing(excludeItemTypes, BaseItemKind.MusicGenre); } - if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase) || includeItemTypes.Contains("Person", StringComparer.OrdinalIgnoreCase))) + if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Person))) { if (!query.IncludeMedia) { - AddIfMissing(includeItemTypes, nameof(Person)); + AddIfMissing(includeItemTypes, BaseItemKind.Person); } } else { - AddIfMissing(excludeItemTypes, nameof(Person)); + AddIfMissing(excludeItemTypes, BaseItemKind.Person); } - if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Studio", StringComparer.OrdinalIgnoreCase))) + if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Studio))) { if (!query.IncludeMedia) { - AddIfMissing(includeItemTypes, nameof(Studio)); + AddIfMissing(includeItemTypes, BaseItemKind.Studio); } } else { - AddIfMissing(excludeItemTypes, nameof(Studio)); + AddIfMissing(excludeItemTypes, BaseItemKind.Studio); } - if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains("MusicArtist", StringComparer.OrdinalIgnoreCase))) + if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.MusicArtist))) { if (!query.IncludeMedia) { - AddIfMissing(includeItemTypes, nameof(MusicArtist)); + AddIfMissing(includeItemTypes, BaseItemKind.MusicArtist); } } else { - AddIfMissing(excludeItemTypes, nameof(MusicArtist)); + AddIfMissing(excludeItemTypes, BaseItemKind.MusicArtist); } - AddIfMissing(excludeItemTypes, nameof(CollectionFolder)); - AddIfMissing(excludeItemTypes, nameof(Folder)); + AddIfMissing(excludeItemTypes, BaseItemKind.CollectionFolder); + AddIfMissing(excludeItemTypes, BaseItemKind.Folder); var mediaTypes = query.MediaTypes.ToList(); if (includeItemTypes.Count > 0) @@ -183,7 +183,7 @@ namespace Emby.Server.Implementations.Library List<BaseItem> mediaItems; - if (searchQuery.IncludeItemTypes.Length == 1 && string.Equals(searchQuery.IncludeItemTypes[0], "MusicArtist", StringComparison.OrdinalIgnoreCase)) + if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist) { if (!searchQuery.ParentId.Equals(Guid.Empty)) { @@ -192,7 +192,7 @@ namespace Emby.Server.Implementations.Library searchQuery.ParentId = Guid.Empty; searchQuery.IncludeItemsByName = true; - searchQuery.IncludeItemTypes = Array.Empty<string>(); + searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>(); mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList(); } else diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 711647e7a6..3593986a9c 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -300,11 +300,11 @@ namespace Emby.Server.Implementations.Library { if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))) { - includeItemTypes = new string[] { "Movie" }; + includeItemTypes = new[] { BaseItemKind.Movie }; } else if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))) { - includeItemTypes = new string[] { "Episode" }; + includeItemTypes = new[] { BaseItemKind.Episode }; } } } @@ -344,13 +344,13 @@ namespace Emby.Server.Implementations.Library var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[] { - nameof(Person), - nameof(Studio), - nameof(Year), - nameof(MusicGenre), - nameof(Genre) + BaseItemKind.Person, + BaseItemKind.Studio, + BaseItemKind.Year, + BaseItemKind.MusicGenre, + BaseItemKind.Genre } - : Array.Empty<string>(); + : Array.Empty<BaseItemKind>(); var query = new InternalItemsQuery(user) { diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index 40436d9c5d..7591e8391f 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Validators var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(MusicArtist) }, + IncludeItemTypes = new[] { BaseItemKind.MusicArtist }, IsDeadArtist = true, IsLocked = false }).Cast<MusicArtist>().ToList(); diff --git a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs index 945b559ad3..73e58d16c3 100644 --- a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Validators var movies = _libraryManager.GetItemList(new InternalItemsQuery { MediaTypes = new string[] { MediaType.Video }, - IncludeItemTypes = new[] { nameof(Movie) }, + IncludeItemTypes = new[] { BaseItemKind.Movie }, IsVirtualItem = false, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, Parent = library, @@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.Library.Validators var boxSets = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(BoxSet) }, + IncludeItemTypes = new[] { BaseItemKind.BoxSet }, CollapseBoxSetItems = false, Recursive = true }); diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index 8a9a4b8652..601aab5b94 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Person) }, + IncludeItemTypes = new[] { BaseItemKind.Person }, IsDeadPerson = true, IsLocked = false }); diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 8577d722e0..26bc49c1f0 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; @@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Validators var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Studio) }, + IncludeItemTypes = new[] { BaseItemKind.Studio }, IsDeadStudio = true, IsLocked = false }); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 644f9050d6..e604000ad2 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1778,7 +1778,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var program = string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(LiveTvProgram) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, Limit = 1, ExternalId = timer.ProgramId, DtoOptions = new DtoOptions(true) @@ -2137,7 +2137,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var query = new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, Limit = 1, DtoOptions = new DtoOptions(true) { @@ -2352,7 +2352,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var query = new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, ExternalSeriesId = seriesTimer.SeriesId, DtoOptions = new DtoOptions(true) { @@ -2387,7 +2387,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV channel = _libraryManager.GetItemList( new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(LiveTvChannel) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel }, ItemIds = new[] { parent.ChannelId }, DtoOptions = new DtoOptions() }).FirstOrDefault() as LiveTvChannel; @@ -2446,7 +2446,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV channel = _libraryManager.GetItemList( new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(LiveTvChannel) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel }, ItemIds = new[] { programInfo.ChannelId }, DtoOptions = new DtoOptions() }).FirstOrDefault() as LiveTvChannel; @@ -2511,7 +2511,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var seriesIds = _libraryManager.GetItemIds( new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Series) }, + IncludeItemTypes = new[] { BaseItemKind.Series }, Name = program.Name }).ToArray(); @@ -2524,7 +2524,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var result = _libraryManager.GetItemIds(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Episode) }, + IncludeItemTypes = new[] { BaseItemKind.Episode }, ParentIndexNumber = program.SeasonNumber.Value, IndexNumber = program.EpisodeNumber.Value, AncestorIds = seriesIds, diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 598e3f88a6..317bcbfdd2 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Drawing; @@ -161,7 +162,7 @@ namespace Emby.Server.Implementations.LiveTv { var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(Series) }, + IncludeItemTypes = new[] { BaseItemKind.Series }, Name = seriesName, Limit = 1, ImageTypes = new ImageType[] { ImageType.Thumb }, @@ -204,7 +205,7 @@ namespace Emby.Server.Implementations.LiveTv var program = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, ExternalSeriesId = programSeriesId, Limit = 1, ImageTypes = new ImageType[] { ImageType.Primary }, @@ -255,7 +256,7 @@ namespace Emby.Server.Implementations.LiveTv { var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(Series) }, + IncludeItemTypes = new[] { BaseItemKind.Series }, Name = seriesName, Limit = 1, ImageTypes = new ImageType[] { ImageType.Thumb }, @@ -298,7 +299,7 @@ namespace Emby.Server.Implementations.LiveTv var program = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(Series) }, + IncludeItemTypes = new[] { BaseItemKind.Series }, Name = seriesName, Limit = 1, ImageTypes = new ImageType[] { ImageType.Primary }, @@ -309,7 +310,7 @@ namespace Emby.Server.Implementations.LiveTv { program = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, ExternalSeriesId = programSeriesId, Limit = 1, ImageTypes = new ImageType[] { ImageType.Primary }, diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index a41b63f284..2f826c63e4 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.LiveTv IsKids = query.IsKids, IsSports = query.IsSports, IsSeries = query.IsSeries, - IncludeItemTypes = new[] { nameof(LiveTvChannel) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel }, TopParentIds = new[] { topFolder.Id }, IsFavorite = query.IsFavorite, IsLiked = query.IsLiked, @@ -810,7 +810,7 @@ namespace Emby.Server.Implementations.LiveTv var internalQuery = new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(LiveTvProgram) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, MinEndDate = query.MinEndDate, MinStartDate = query.MinStartDate, MaxEndDate = query.MaxEndDate, @@ -874,7 +874,7 @@ namespace Emby.Server.Implementations.LiveTv var internalQuery = new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(LiveTvProgram) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, IsAiring = query.IsAiring, HasAired = query.HasAired, IsNews = query.IsNews, @@ -1085,8 +1085,8 @@ namespace Emby.Server.Implementations.LiveTv if (cleanDatabase) { - CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { nameof(LiveTvChannel) }, progress, cancellationToken); - CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { nameof(LiveTvProgram) }, progress, cancellationToken); + CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { BaseItemKind.LiveTvChannel }, progress, cancellationToken); + CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { BaseItemKind.LiveTvProgram }, progress, cancellationToken); } var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault(); @@ -1177,7 +1177,7 @@ namespace Emby.Server.Implementations.LiveTv var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, ChannelIds = new Guid[] { currentChannel.Id }, DtoOptions = new DtoOptions(true) }).Cast<LiveTvProgram>().ToDictionary(i => i.Id); @@ -1261,7 +1261,7 @@ namespace Emby.Server.Implementations.LiveTv return new Tuple<List<Guid>, List<Guid>>(channels, programs); } - private void CleanDatabaseInternal(Guid[] currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken) + private void CleanDatabaseInternal(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress<double> progress, CancellationToken cancellationToken) { var list = _itemRepo.GetItemIdsList(new InternalItemsQuery { @@ -1328,25 +1328,25 @@ namespace Emby.Server.Implementations.LiveTv .Select(i => i.Id) .ToList(); - var excludeItemTypes = new List<string>(); + var excludeItemTypes = new List<BaseItemKind>(); if (folderIds.Count == 0) { return new QueryResult<BaseItem>(); } - var includeItemTypes = new List<string>(); + var includeItemTypes = new List<BaseItemKind>(); var genres = new List<string>(); if (query.IsMovie.HasValue) { if (query.IsMovie.Value) { - includeItemTypes.Add(nameof(Movie)); + includeItemTypes.Add(BaseItemKind.Movie); } else { - excludeItemTypes.Add(nameof(Movie)); + excludeItemTypes.Add(BaseItemKind.Movie); } } @@ -1354,11 +1354,11 @@ namespace Emby.Server.Implementations.LiveTv { if (query.IsSeries.Value) { - includeItemTypes.Add(nameof(Episode)); + includeItemTypes.Add(BaseItemKind.Episode); } else { - excludeItemTypes.Add(nameof(Episode)); + excludeItemTypes.Add(BaseItemKind.Episode); } } @@ -1878,7 +1878,7 @@ namespace Emby.Server.Implementations.LiveTv var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(LiveTvProgram) }, + IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram }, ChannelIds = channelIds, MaxStartDate = now, MinEndDate = now, diff --git a/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs index 8b1cee89d1..8ec9f61616 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Querying; @@ -45,7 +46,7 @@ namespace Emby.Server.Implementations.Playlists } query.Recursive = true; - query.IncludeItemTypes = new[] { "Playlist" }; + query.IncludeItemTypes = new[] { BaseItemKind.Playlist }; query.Parent = null; return LibraryManager.GetItemsResult(query); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 341d2c8e61..d10a24bbc6 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1292,7 +1292,7 @@ namespace Emby.Server.Implementations.Session { ["ItemId"] = command.ItemId, ["ItemName"] = command.ItemName, - ["ItemType"] = command.ItemType + ["ItemType"] = command.ItemType.ToString() } }; diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 4d990c8718..a878312944 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.TV .GetItemList( new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(Episode) }, + IncludeItemTypes = new[] { BaseItemKind.Episode }, OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) }, SeriesPresentationUniqueKey = presentationUniqueKey, Limit = limit, @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.TV { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, - IncludeItemTypes = new[] { nameof(Episode) }, + IncludeItemTypes = new[] { BaseItemKind.Episode }, OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) }, IsPlayed = true, Limit = 1, @@ -209,7 +209,7 @@ namespace Emby.Server.Implementations.TV { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, - IncludeItemTypes = new[] { nameof(Episode) }, + IncludeItemTypes = new[] { BaseItemKind.Episode }, OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }, Limit = 1, IsPlayed = false, @@ -226,7 +226,7 @@ namespace Emby.Server.Implementations.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, ParentIndexNumber = 0, - IncludeItemTypes = new[] { nameof(Episode) }, + IncludeItemTypes = new[] { BaseItemKind.Episode }, IsPlayed = false, IsVirtualItem = false, DtoOptions = dtoOptions diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 154a567020..3df9755632 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -133,8 +133,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, MediaTypes = mediaTypes, StartIndex = startIndex, Limit = limit, @@ -337,8 +337,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, MediaTypes = mediaTypes, StartIndex = startIndex, Limit = limit, diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index 223b2a2b66..02a0785e76 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers { User = user, MediaTypes = mediaTypes, - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + IncludeItemTypes = includeItemTypes, Recursive = true, EnableTotalRecordCount = false, DtoOptions = new DtoOptions @@ -166,7 +166,7 @@ namespace Jellyfin.Api.Controllers var filters = new QueryFilters(); var genreQuery = new InternalItemsQuery(user) { - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + IncludeItemTypes = includeItemTypes, DtoOptions = new DtoOptions { Fields = Array.Empty<ItemFields>(), diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 5aa457153c..37e6ae1849 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -101,8 +101,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, @@ -160,7 +160,7 @@ namespace Jellyfin.Api.Controllers Genre item = new Genre(); if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1) { - var result = GetItemFromSlugName<Genre>(_libraryManager, genreName, dtoOptions); + var result = GetItemFromSlugName<Genre>(_libraryManager, genreName, dtoOptions, BaseItemKind.Genre); if (result != null) { @@ -182,27 +182,27 @@ namespace Jellyfin.Api.Controllers return _dtoService.GetBaseItemDto(item, dtoOptions); } - private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions) + private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions, BaseItemKind baseItemKind) where T : BaseItem, new() { var result = libraryManager.GetItemList(new InternalItemsQuery { Name = name.Replace(BaseItem.SlugChar, '&'), - IncludeItemTypes = new[] { typeof(T).Name }, + IncludeItemTypes = new[] { baseItemKind }, DtoOptions = dtoOptions }).OfType<T>().FirstOrDefault(); result ??= libraryManager.GetItemList(new InternalItemsQuery { Name = name.Replace(BaseItem.SlugChar, '/'), - IncludeItemTypes = new[] { typeof(T).Name }, + IncludeItemTypes = new[] { baseItemKind }, DtoOptions = dtoOptions }).OfType<T>().FirstOrDefault(); result ??= libraryManager.GetItemList(new InternalItemsQuery { Name = name.Replace(BaseItem.SlugChar, '?'), - IncludeItemTypes = new[] { typeof(T).Name }, + IncludeItemTypes = new[] { baseItemKind }, DtoOptions = dtoOptions }).OfType<T>().FirstOrDefault(); diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 45a36c8fe1..22e3fd202e 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -296,8 +296,8 @@ namespace Jellyfin.Api.Controllers { IsPlayed = isPlayed, MediaTypes = mediaTypes, - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), - ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = excludeItemTypes, Recursive = recursive ?? false, OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), IsFavorite = isFavorite, @@ -459,7 +459,7 @@ namespace Jellyfin.Api.Controllers { query.AlbumIds = albums.SelectMany(i => { - return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { nameof(MusicAlbum) }, Name = i, Limit = 1 }); + return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Name = i, Limit = 1 }); }).ToArray(); } @@ -483,7 +483,7 @@ namespace Jellyfin.Api.Controllers if (query.OrderBy.Count == 0) { // Albums by artist - if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], "MusicAlbum", StringComparison.OrdinalIgnoreCase)) + if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.MusicAlbum) { query.OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.ProductionYear, SortOrder.Descending), new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }; } @@ -831,8 +831,8 @@ namespace Jellyfin.Api.Controllers CollapseBoxSetItems = false, EnableTotalRecordCount = enableTotalRecordCount, AncestorIds = ancestorIds, - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), - ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = excludeItemTypes, SearchTerm = searchTerm, ExcludeItemIds = excludeItemIds }); diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 0be853ca4f..d4cc3810a0 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -14,6 +14,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LibraryDtos; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -413,14 +414,14 @@ namespace Jellyfin.Api.Controllers var counts = new ItemCounts { - AlbumCount = GetCount(typeof(MusicAlbum), user, isFavorite), - EpisodeCount = GetCount(typeof(Episode), user, isFavorite), - MovieCount = GetCount(typeof(Movie), user, isFavorite), - SeriesCount = GetCount(typeof(Series), user, isFavorite), - SongCount = GetCount(typeof(Audio), user, isFavorite), - MusicVideoCount = GetCount(typeof(MusicVideo), user, isFavorite), - BoxSetCount = GetCount(typeof(BoxSet), user, isFavorite), - BookCount = GetCount(typeof(Book), user, isFavorite) + AlbumCount = GetCount(BaseItemKind.MusicAlbum, user, isFavorite), + EpisodeCount = GetCount(BaseItemKind.Episode, user, isFavorite), + MovieCount = GetCount(BaseItemKind.Movie, user, isFavorite), + SeriesCount = GetCount(BaseItemKind.Series, user, isFavorite), + SongCount = GetCount(BaseItemKind.Audio, user, isFavorite), + MusicVideoCount = GetCount(BaseItemKind.MusicVideo, user, isFavorite), + BoxSetCount = GetCount(BaseItemKind.BoxSet, user, isFavorite), + BookCount = GetCount(BaseItemKind.Book, user, isFavorite) }; return counts; @@ -529,7 +530,7 @@ namespace Jellyfin.Api.Controllers { var series = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Series) }, + IncludeItemTypes = new[] { BaseItemKind.Series }, DtoOptions = new DtoOptions(false) { EnableImages = false @@ -559,7 +560,7 @@ namespace Jellyfin.Api.Controllers { var movies = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(Movie) }, + IncludeItemTypes = new[] { BaseItemKind.Movie }, DtoOptions = new DtoOptions(false) { EnableImages = false @@ -715,26 +716,26 @@ namespace Jellyfin.Api.Controllers bool? isMovie = item is Movie || (program != null && program.IsMovie) || item is Trailer; bool? isSeries = item is Series || (program != null && program.IsSeries); - var includeItemTypes = new List<string>(); + var includeItemTypes = new List<BaseItemKind>(); if (isMovie.Value) { - includeItemTypes.Add(nameof(Movie)); + includeItemTypes.Add(BaseItemKind.Movie); if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) { - includeItemTypes.Add(nameof(Trailer)); - includeItemTypes.Add(nameof(LiveTvProgram)); + includeItemTypes.Add(BaseItemKind.Trailer); + includeItemTypes.Add(BaseItemKind.LiveTvProgram); } } else if (isSeries.Value) { - includeItemTypes.Add(nameof(Series)); + includeItemTypes.Add(BaseItemKind.Series); } else { // For non series and movie types these columns are typically null isSeries = null; isMovie = null; - includeItemTypes.Add(item.GetType().Name); + includeItemTypes.Add(item.GetBaseItemKind()); } var query = new InternalItemsQuery(user) @@ -871,11 +872,11 @@ namespace Jellyfin.Api.Controllers return result; } - private int GetCount(Type type, User? user, bool? isFavorite) + private int GetCount(BaseItemKind itemKind, User? user, bool? isFavorite) { var query = new InternalItemsQuery(user) { - IncludeItemTypes = new[] { type.Name }, + IncludeItemTypes = new[] { itemKind }, Limit = 0, Recursive = true, IsVirtualItem = false, diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index 99c90d19e7..def10f0bd6 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers { IncludeItemTypes = new[] { - nameof(Movie), + BaseItemKind.Movie, // nameof(Trailer), // nameof(LiveTvProgram) }, @@ -99,11 +99,11 @@ namespace Jellyfin.Api.Controllers var recentlyPlayedMovies = _libraryManager.GetItemList(query); - var itemTypes = new List<string> { nameof(Movie) }; + var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie }; if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) { - itemTypes.Add(nameof(Trailer)); - itemTypes.Add(nameof(LiveTvProgram)); + itemTypes.Add(BaseItemKind.Trailer); + itemTypes.Add(BaseItemKind.LiveTvProgram); } var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user) @@ -182,11 +182,11 @@ namespace Jellyfin.Api.Controllers DtoOptions dtoOptions, RecommendationType type) { - var itemTypes = new List<string> { nameof(Movie) }; + var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie }; if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) { - itemTypes.Add(nameof(Trailer)); - itemTypes.Add(nameof(LiveTvProgram)); + itemTypes.Add(BaseItemKind.Trailer); + itemTypes.Add(BaseItemKind.LiveTvProgram); } foreach (var name in names) @@ -224,11 +224,11 @@ namespace Jellyfin.Api.Controllers private IEnumerable<RecommendationDto> GetWithActor(User? user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) { - var itemTypes = new List<string> { nameof(Movie) }; + var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie }; if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) { - itemTypes.Add(nameof(Trailer)); - itemTypes.Add(nameof(LiveTvProgram)); + itemTypes.Add(BaseItemKind.Trailer); + itemTypes.Add(BaseItemKind.LiveTvProgram); } foreach (var name in names) @@ -264,11 +264,11 @@ namespace Jellyfin.Api.Controllers private IEnumerable<RecommendationDto> GetSimilarTo(User? user, IEnumerable<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type) { - var itemTypes = new List<string> { nameof(Movie) }; + var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie }; if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) { - itemTypes.Add(nameof(Trailer)); - itemTypes.Add(nameof(LiveTvProgram)); + itemTypes.Add(BaseItemKind.Trailer); + itemTypes.Add(BaseItemKind.LiveTvProgram); } foreach (var item in baselineItems) diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 27eec2b9a8..c4c03aa4f5 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -101,8 +101,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, @@ -149,7 +149,7 @@ namespace Jellyfin.Api.Controllers if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1) { - item = GetItemFromSlugName<MusicGenre>(_libraryManager, genreName, dtoOptions); + item = GetItemFromSlugName<MusicGenre>(_libraryManager, genreName, dtoOptions, BaseItemKind.MusicGenre); } else { @@ -166,27 +166,27 @@ namespace Jellyfin.Api.Controllers return _dtoService.GetBaseItemDto(item, dtoOptions); } - private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions) + private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions, BaseItemKind baseItemKind) where T : BaseItem, new() { var result = libraryManager.GetItemList(new InternalItemsQuery { Name = name.Replace(BaseItem.SlugChar, '&'), - IncludeItemTypes = new[] { typeof(T).Name }, + IncludeItemTypes = new[] { baseItemKind }, DtoOptions = dtoOptions }).OfType<T>().FirstOrDefault(); result ??= libraryManager.GetItemList(new InternalItemsQuery { Name = name.Replace(BaseItem.SlugChar, '/'), - IncludeItemTypes = new[] { typeof(T).Name }, + IncludeItemTypes = new[] { baseItemKind }, DtoOptions = dtoOptions }).OfType<T>().FirstOrDefault(); result ??= libraryManager.GetItemList(new InternalItemsQuery { Name = name.Replace(BaseItem.SlugChar, '?'), - IncludeItemTypes = new[] { typeof(T).Name }, + IncludeItemTypes = new[] { baseItemKind }, DtoOptions = dtoOptions }).OfType<T>().FirstOrDefault(); diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 73bdf90182..02ee7860f7 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -110,8 +110,8 @@ namespace Jellyfin.Api.Controllers IncludeStudios = includeStudios, StartIndex = startIndex, UserId = userId ?? Guid.Empty, - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), - ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = excludeItemTypes, MediaTypes = mediaTypes, ParentId = parentId, diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 3a04cb3a4a..a6bbd40ccc 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -127,7 +127,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<ActionResult> DisplayContent( [FromRoute, Required] string sessionId, - [FromQuery, Required] string itemType, + [FromQuery, Required] BaseItemKind itemType, [FromQuery, Required] string itemId, [FromQuery, Required] string itemName) { diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index da8f8b1995..4422ef32ca 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -97,8 +97,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index 97eec4bd2e..af77c801f7 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -58,7 +58,7 @@ namespace Jellyfin.Api.Controllers public ActionResult<QueryResult<BaseItemDto>> GetSuggestions( [FromRoute, Required] Guid userId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaType, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] type, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] type, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] bool enableTotalRecordCount = false) diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 5dd7733316..f47e71bd17 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -157,7 +157,7 @@ namespace Jellyfin.Api.Controllers var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(Episode) }, + IncludeItemTypes = new[] { BaseItemKind.Episode }, OrderBy = new[] { (ItemSortBy.PremiereDate, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending) }, MinPremiereDate = minPremiereDate, StartIndex = startIndex, diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index a33a0826c9..7e182d0e82 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -297,7 +297,7 @@ namespace Jellyfin.Api.Controllers new LatestItemsQuery { GroupItems = groupItems, - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + IncludeItemTypes = includeItemTypes, IsPlayed = isPlayed, Limit = limit, ParentId = parentId ?? Guid.Empty, diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index d6dc6650cf..2bba2b97d7 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -101,8 +101,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), - IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = includeItemTypes, MediaTypes = mediaTypes, DtoOptions = dtoOptions }; diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 0efd3443b8..ca8bc0bdd3 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -137,21 +137,5 @@ namespace Jellyfin.Api.Helpers TotalRecordCount = result.TotalRecordCount }; } - - internal static string[] GetItemTypeStrings(IReadOnlyList<BaseItemKind> itemKinds) - { - if (itemKinds.Count == 0) - { - return Array.Empty<string>(); - } - - var itemTypes = new string[itemKinds.Count]; - for (var i = 0; i < itemKinds.Count; i++) - { - itemTypes[i] = itemKinds[i].ToString(); - } - - return itemTypes; - } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index f30f8ce7f2..11b95b94b8 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -88,7 +88,7 @@ namespace MediaBrowser.Controller.Entities.Audio { if (query.IncludeItemTypes.Length == 0) { - query.IncludeItemTypes = new[] { nameof(Audio), nameof(MusicVideo), nameof(MusicAlbum) }; + query.IncludeItemTypes = new[] { BaseItemKind.Audio, BaseItemKind.MusicVideo, BaseItemKind.MusicAlbum }; query.ArtistIds = new[] { Id }; } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index dc6fcc55a5..73a25232e4 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; using Diacritics.Extensions; +using Jellyfin.Data.Enums; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities.Audio @@ -66,7 +67,7 @@ namespace MediaBrowser.Controller.Entities.Audio public IList<BaseItem> GetTaggedItems(InternalItemsQuery query) { query.GenreIds = new[] { Id }; - query.IncludeItemTypes = new[] { nameof(MusicVideo), nameof(Audio), nameof(MusicAlbum), nameof(MusicArtist) }; + query.IncludeItemTypes = new[] { BaseItemKind.MusicVideo, BaseItemKind.Audio, BaseItemKind.MusicAlbum, BaseItemKind.MusicArtist }; return LibraryManager.GetItemList(query); } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index ec1ebaabec..55551e70ee 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -792,7 +792,7 @@ namespace MediaBrowser.Controller.Entities private bool RequiresPostFiltering2(InternalItemsQuery query) { - if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase)) + if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.BoxSet) { Logger.LogDebug("Query requires post-filtering due to BoxSet query"); return true; @@ -882,7 +882,7 @@ namespace MediaBrowser.Controller.Entities if (query.IsPlayed.HasValue) { - if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(nameof(Series))) + if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(BaseItemKind.Series)) { Logger.LogDebug("Query requires post-filtering due to IsPlayed"); return true; @@ -1101,7 +1101,7 @@ namespace MediaBrowser.Controller.Entities return false; } - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains("Movie", StringComparer.OrdinalIgnoreCase)) + if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Movie)) { param = true; } diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 338f96204d..4be6732372 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; using Diacritics.Extensions; -using MediaBrowser.Controller.Entities.Audio; +using Jellyfin.Data.Enums; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities @@ -66,10 +66,10 @@ namespace MediaBrowser.Controller.Entities query.GenreIds = new[] { Id }; query.ExcludeItemTypes = new[] { - nameof(MusicVideo), - nameof(Entities.Audio.Audio), - nameof(MusicAlbum), - nameof(MusicArtist) + BaseItemKind.MusicVideo, + BaseItemKind.Audio, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicArtist }; return LibraryManager.GetItemList(query); diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 0baa7725e1..f06b5c7875 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -27,13 +27,13 @@ namespace MediaBrowser.Controller.Entities ExcludeArtistIds = Array.Empty<Guid>(); ExcludeInheritedTags = Array.Empty<string>(); ExcludeItemIds = Array.Empty<Guid>(); - ExcludeItemTypes = Array.Empty<string>(); + ExcludeItemTypes = Array.Empty<BaseItemKind>(); ExcludeTags = Array.Empty<string>(); GenreIds = Array.Empty<Guid>(); Genres = Array.Empty<string>(); GroupByPresentationUniqueKey = true; ImageTypes = Array.Empty<ImageType>(); - IncludeItemTypes = Array.Empty<string>(); + IncludeItemTypes = Array.Empty<BaseItemKind>(); ItemIds = Array.Empty<Guid>(); MediaTypes = Array.Empty<string>(); MinSimilarityScore = 20; @@ -87,9 +87,9 @@ namespace MediaBrowser.Controller.Entities public string[] MediaTypes { get; set; } - public string[] IncludeItemTypes { get; set; } + public BaseItemKind[] IncludeItemTypes { get; set; } - public string[] ExcludeItemTypes { get; set; } + public BaseItemKind[] ExcludeItemTypes { get; set; } public string[] ExcludeTags { get; set; } @@ -229,7 +229,7 @@ namespace MediaBrowser.Controller.Entities public Guid ParentId { get; set; } - public string? ParentType { get; set; } + public BaseItemKind? ParentType { get; set; } public Guid[] AncestorIds { get; set; } @@ -314,7 +314,7 @@ namespace MediaBrowser.Controller.Entities else { ParentId = value.Id; - ParentType = value.GetType().Name; + ParentType = value.GetBaseItemKind(); } } } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index e4933e9682..dd2c464e9e 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -131,7 +131,7 @@ namespace MediaBrowser.Controller.Entities.TV { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, - IncludeItemTypes = new[] { nameof(Season) }, + IncludeItemTypes = new[] { BaseItemKind.Season }, IsVirtualItem = false, Limit = 0, DtoOptions = new DtoOptions(false) @@ -159,7 +159,7 @@ namespace MediaBrowser.Controller.Entities.TV if (query.IncludeItemTypes.Length == 0) { - query.IncludeItemTypes = new[] { nameof(Episode) }; + query.IncludeItemTypes = new[] { BaseItemKind.Episode }; } query.IsVirtualItem = false; @@ -213,7 +213,7 @@ namespace MediaBrowser.Controller.Entities.TV query.AncestorWithPresentationUniqueKey = null; query.SeriesPresentationUniqueKey = seriesKey; - query.IncludeItemTypes = new[] { nameof(Season) }; + query.IncludeItemTypes = new[] { BaseItemKind.Season }; query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }; if (user != null && !user.DisplayMissingEpisodes) @@ -239,7 +239,7 @@ namespace MediaBrowser.Controller.Entities.TV if (query.IncludeItemTypes.Length == 0) { - query.IncludeItemTypes = new[] { nameof(Episode), nameof(Season) }; + query.IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Season }; } query.IsVirtualItem = false; @@ -259,7 +259,7 @@ namespace MediaBrowser.Controller.Entities.TV { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, - IncludeItemTypes = new[] { nameof(Episode), nameof(Season) }, + IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Season }, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, DtoOptions = options }; @@ -363,7 +363,7 @@ namespace MediaBrowser.Controller.Entities.TV { AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey, SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null, - IncludeItemTypes = new[] { nameof(Episode) }, + IncludeItemTypes = new[] { BaseItemKind.Episode }, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, DtoOptions = options }; diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 1cff720370..13ffbf535c 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.Controller.Entities if (query.IncludeItemTypes.Length == 0) { - query.IncludeItemTypes = new[] { nameof(Movie) }; + query.IncludeItemTypes = new[] { BaseItemKind.Movie }; } return parent.QueryRecursive(query); @@ -165,7 +165,7 @@ namespace MediaBrowser.Controller.Entities query.Parent = parent; query.SetUser(user); query.IsFavorite = true; - query.IncludeItemTypes = new[] { nameof(Movie) }; + query.IncludeItemTypes = new[] { BaseItemKind.Movie }; return _libraryManager.GetItemsResult(query); } @@ -176,7 +176,7 @@ namespace MediaBrowser.Controller.Entities query.Parent = parent; query.SetUser(user); query.IsFavorite = true; - query.IncludeItemTypes = new[] { nameof(Series) }; + query.IncludeItemTypes = new[] { BaseItemKind.Series }; return _libraryManager.GetItemsResult(query); } @@ -187,7 +187,7 @@ namespace MediaBrowser.Controller.Entities query.Parent = parent; query.SetUser(user); query.IsFavorite = true; - query.IncludeItemTypes = new[] { nameof(Episode) }; + query.IncludeItemTypes = new[] { BaseItemKind.Episode }; return _libraryManager.GetItemsResult(query); } @@ -198,7 +198,7 @@ namespace MediaBrowser.Controller.Entities query.Parent = parent; query.SetUser(user); - query.IncludeItemTypes = new[] { nameof(Movie) }; + query.IncludeItemTypes = new[] { BaseItemKind.Movie }; return _libraryManager.GetItemsResult(query); } @@ -206,7 +206,7 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetMovieCollections(User user, InternalItemsQuery query) { query.Parent = null; - query.IncludeItemTypes = new[] { nameof(BoxSet) }; + query.IncludeItemTypes = new[] { BaseItemKind.BoxSet }; query.SetUser(user); query.Recursive = true; @@ -220,7 +220,7 @@ namespace MediaBrowser.Controller.Entities query.Parent = parent; query.SetUser(user); query.Limit = GetSpecialItemsLimit(); - query.IncludeItemTypes = new[] { nameof(Movie) }; + query.IncludeItemTypes = new[] { BaseItemKind.Movie }; return ConvertToResult(_libraryManager.GetItemList(query)); } @@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Entities query.Parent = parent; query.SetUser(user); query.Limit = GetSpecialItemsLimit(); - query.IncludeItemTypes = new[] { nameof(Movie) }; + query.IncludeItemTypes = new[] { BaseItemKind.Movie }; return ConvertToResult(_libraryManager.GetItemList(query)); } @@ -252,7 +252,7 @@ namespace MediaBrowser.Controller.Entities { var genres = parent.QueryRecursive(new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(Movie) }, + IncludeItemTypes = new[] { BaseItemKind.Movie }, Recursive = true, EnableTotalRecordCount = false }).Items @@ -283,7 +283,7 @@ namespace MediaBrowser.Controller.Entities query.GenreIds = new[] { displayParent.Id }; query.SetUser(user); - query.IncludeItemTypes = new[] { nameof(Movie) }; + query.IncludeItemTypes = new[] { BaseItemKind.Movie }; return _libraryManager.GetItemsResult(query); } @@ -299,9 +299,9 @@ namespace MediaBrowser.Controller.Entities { query.IncludeItemTypes = new[] { - nameof(Series), - nameof(Season), - nameof(Episode) + BaseItemKind.Series, + BaseItemKind.Season, + BaseItemKind.Episode }; } @@ -329,7 +329,7 @@ namespace MediaBrowser.Controller.Entities query.Parent = parent; query.SetUser(user); query.Limit = GetSpecialItemsLimit(); - query.IncludeItemTypes = new[] { nameof(Episode) }; + query.IncludeItemTypes = new[] { BaseItemKind.Episode }; query.IsVirtualItem = false; return ConvertToResult(_libraryManager.GetItemList(query)); @@ -360,7 +360,7 @@ namespace MediaBrowser.Controller.Entities query.Parent = parent; query.SetUser(user); query.Limit = GetSpecialItemsLimit(); - query.IncludeItemTypes = new[] { nameof(Episode) }; + query.IncludeItemTypes = new[] { BaseItemKind.Episode }; return ConvertToResult(_libraryManager.GetItemList(query)); } @@ -371,7 +371,7 @@ namespace MediaBrowser.Controller.Entities query.Parent = parent; query.SetUser(user); - query.IncludeItemTypes = new[] { nameof(Series) }; + query.IncludeItemTypes = new[] { BaseItemKind.Series }; return _libraryManager.GetItemsResult(query); } @@ -380,7 +380,7 @@ namespace MediaBrowser.Controller.Entities { var genres = parent.QueryRecursive(new InternalItemsQuery(user) { - IncludeItemTypes = new[] { nameof(Series) }, + IncludeItemTypes = new[] { BaseItemKind.Series }, Recursive = true, EnableTotalRecordCount = false }).Items @@ -411,7 +411,7 @@ namespace MediaBrowser.Controller.Entities query.GenreIds = new[] { displayParent.Id }; query.SetUser(user); - query.IncludeItemTypes = new[] { nameof(Series) }; + query.IncludeItemTypes = new[] { BaseItemKind.Series }; return _libraryManager.GetItemsResult(query); } @@ -499,12 +499,12 @@ namespace MediaBrowser.Controller.Entities return false; } - if (query.IncludeItemTypes.Length > 0 && !query.IncludeItemTypes.Contains(item.GetClientTypeName(), StringComparer.OrdinalIgnoreCase)) + if (query.IncludeItemTypes.Length > 0 && !query.IncludeItemTypes.Contains(item.GetBaseItemKind())) { return false; } - if (query.ExcludeItemTypes.Length > 0 && query.ExcludeItemTypes.Contains(item.GetClientTypeName(), StringComparer.OrdinalIgnoreCase)) + if (query.ExcludeItemTypes.Length > 0 && query.ExcludeItemTypes.Contains(item.GetBaseItemKind())) { return false; } diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 5e671a725d..89f3bdf46b 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -189,7 +189,7 @@ namespace MediaBrowser.Controller.Playlists return LibraryManager.GetItemList(new InternalItemsQuery(user) { Recursive = true, - IncludeItemTypes = new[] { nameof(Audio) }, + IncludeItemTypes = new[] { BaseItemKind.Audio }, GenreIds = new[] { musicGenre.Id }, OrderBy = new[] { (ItemSortBy.AlbumArtist, SortOrder.Ascending), (ItemSortBy.Album, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending) }, DtoOptions = options @@ -201,7 +201,7 @@ namespace MediaBrowser.Controller.Playlists return LibraryManager.GetItemList(new InternalItemsQuery(user) { Recursive = true, - IncludeItemTypes = new[] { nameof(Audio) }, + IncludeItemTypes = new[] { BaseItemKind.Audio }, ArtistIds = new[] { musicArtist.Id }, OrderBy = new[] { (ItemSortBy.AlbumArtist, SortOrder.Ascending), (ItemSortBy.Album, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending) }, DtoOptions = options diff --git a/MediaBrowser.Model/Querying/LatestItemsQuery.cs b/MediaBrowser.Model/Querying/LatestItemsQuery.cs index f555ffb36d..d2d9f1f9ae 100644 --- a/MediaBrowser.Model/Querying/LatestItemsQuery.cs +++ b/MediaBrowser.Model/Querying/LatestItemsQuery.cs @@ -2,6 +2,7 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Querying @@ -48,7 +49,7 @@ namespace MediaBrowser.Model.Querying /// Gets or sets the include item types. /// </summary> /// <value>The include item types.</value> - public string[] IncludeItemTypes { get; set; } + public BaseItemKind[] IncludeItemTypes { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is played. diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs index aedfa4d363..1caed827f3 100644 --- a/MediaBrowser.Model/Search/SearchQuery.cs +++ b/MediaBrowser.Model/Search/SearchQuery.cs @@ -2,6 +2,7 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Search { @@ -16,8 +17,8 @@ namespace MediaBrowser.Model.Search IncludeStudios = true; MediaTypes = Array.Empty<string>(); - IncludeItemTypes = Array.Empty<string>(); - ExcludeItemTypes = Array.Empty<string>(); + IncludeItemTypes = Array.Empty<BaseItemKind>(); + ExcludeItemTypes = Array.Empty<BaseItemKind>(); } /// <summary> @@ -56,9 +57,9 @@ namespace MediaBrowser.Model.Search public string[] MediaTypes { get; set; } - public string[] IncludeItemTypes { get; set; } + public BaseItemKind[] IncludeItemTypes { get; set; } - public string[] ExcludeItemTypes { get; set; } + public BaseItemKind[] ExcludeItemTypes { get; set; } public Guid? ParentId { get; set; } diff --git a/MediaBrowser.Model/Session/BrowseRequest.cs b/MediaBrowser.Model/Session/BrowseRequest.cs index 65afe5cf34..5ad7d783a1 100644 --- a/MediaBrowser.Model/Session/BrowseRequest.cs +++ b/MediaBrowser.Model/Session/BrowseRequest.cs @@ -1,3 +1,5 @@ +using Jellyfin.Data.Enums; + #nullable disable namespace MediaBrowser.Model.Session { @@ -8,10 +10,9 @@ namespace MediaBrowser.Model.Session { /// <summary> /// Gets or sets the item type. - /// Artist, Genre, Studio, Person, or any kind of BaseItem. /// </summary> /// <value>The type of the item.</value> - public string ItemType { get; set; } + public BaseItemKind ItemType { get; set; } /// <summary> /// Gets or sets the item id. diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index ca557f6d61..6e57533618 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -11,6 +11,7 @@ using System.Net.Http; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; @@ -1133,7 +1134,7 @@ namespace MediaBrowser.Providers.Manager var albums = _libraryManager .GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { nameof(MusicAlbum) }, + IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, ArtistIds = new[] { item.Id }, DtoOptions = new DtoOptions(false) { diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 1eacbf1e1b..cce71b0673 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -66,7 +67,7 @@ namespace MediaBrowser.Providers.MediaInfo { var options = GetOptions(); - var types = new[] { "Episode", "Movie" }; + var types = new[] { BaseItemKind.Episode, BaseItemKind.Movie }; var dict = new Dictionary<Guid, BaseItem>(); diff --git a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs index 4ba7e1d2ff..c4640bd226 100644 --- a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs +++ b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs @@ -55,35 +55,5 @@ namespace Jellyfin.Api.Tests.Helpers return data; } - - [Fact] - public static void GetItemTypeStrings_Empty_Empty() - { - Assert.Empty(RequestHelpers.GetItemTypeStrings(Array.Empty<BaseItemKind>())); - } - - [Fact] - public static void GetItemTypeStrings_Valid_Success() - { - BaseItemKind[] input = - { - BaseItemKind.AggregateFolder, - BaseItemKind.Audio, - BaseItemKind.BasePluginFolder, - BaseItemKind.CollectionFolder - }; - - string[] expected = - { - "AggregateFolder", - "Audio", - "BasePluginFolder", - "CollectionFolder" - }; - - var res = RequestHelpers.GetItemTypeStrings(input); - - Assert.Equal(expected, res); - } } } From c5569c701c8aaaa70b17c0cf5ce8d425d5e69b5c Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Sun, 12 Dec 2021 19:04:22 +0100 Subject: [PATCH 124/167] Folder can't have extras --- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index ad20ea334f..200f98cc93 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1451,7 +1451,7 @@ namespace MediaBrowser.Controller.Entities /// <returns><c>true</c> if any items have changed, else <c>false</c>.</returns> protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { - if (!IsFileProtocol || !SupportsOwnedItems || IsInMixedFolder || this is ICollectionFolder) + if (!IsFileProtocol || !SupportsOwnedItems || IsInMixedFolder || this is ICollectionFolder || this.GetType() == typeof(Folder)) { return false; } From 3a4e7fb075b2dec5f87a2a50c85cd35f9fd6fd66 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Mon, 13 Dec 2021 00:26:54 +0100 Subject: [PATCH 125/167] semi undo --- .../Images/LocalImageProvider.cs | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 46f1fede3e..3809912ca4 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -106,7 +106,7 @@ namespace MediaBrowser.LocalMetadata.Images { if (!item.IsFileProtocol) { - yield break; + return Enumerable.Empty<FileSystemMetadata>(); } var path = item.ContainingFolderPath; @@ -114,21 +114,14 @@ namespace MediaBrowser.LocalMetadata.Images // Exit if the cache dir does not exist, alternative solution is to create it, but that's a lot of empty dirs... if (!Directory.Exists(path)) { - yield break; + return Enumerable.Empty<FileSystemMetadata>(); } - var files = directoryService.GetFileSystemEntries(path).OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty)); - var count = BaseItem.SupportedImageExtensions.Length; - foreach (var file in files) - { - for (var i = 0; i < count; i++) - { - if ((includeDirectories && file.IsDirectory) || string.Equals(BaseItem.SupportedImageExtensions[i], file.Extension, StringComparison.OrdinalIgnoreCase)) - { - yield return file; - } - } - } + return directoryService.GetFiles(path) + .Where(i => + (includeDirectories && i.IsDirectory) + || Array.FindIndex(BaseItem.SupportedImageExtensions, ext => string.Equals(ext, i.Extension, StringComparison.OrdinalIgnoreCase)) != -1) + .OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty)); } /// <inheritdoc /> From 5c3d0b5510f7c96813bb45acad2452d62fc523b1 Mon Sep 17 00:00:00 2001 From: Claus Vium <cvium@users.noreply.github.com> Date: Mon, 13 Dec 2021 08:22:17 +0100 Subject: [PATCH 126/167] Update Emby.Naming/Video/ExtraResolver.cs Co-authored-by: Bond-009 <bond.009@outlook.com> --- Emby.Naming/Video/ExtraResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index 8dab084f6b..0cf77327c6 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -111,7 +111,7 @@ namespace Emby.Naming.Video continue; } - var currentFile = files[pos].Files[0]; + var currentFile = current.Files[0]; var trimmedCurrentFileName = TrimFilenameDelimiters(currentFile.Name, videoFlagDelimiters); // first check filenames From 9e665ff520de3f537ec5e5e469dfdbbb9aabca29 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Mon, 13 Dec 2021 08:27:20 +0100 Subject: [PATCH 127/167] Fix usage of RegexOptions.Compiled --- Emby.Naming/Video/ExtraResolver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index 0cf77327c6..fbdca859f7 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -60,9 +60,9 @@ namespace Emby.Naming.Video { var filename = Path.GetFileName(path); - var regex = new Regex(rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled); + var isMatch = Regex.IsMatch(filename, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled); - if (regex.IsMatch(filename)) + if (isMatch) { result.ExtraType = rule.ExtraType; result.Rule = rule; From 0120d80b780095e416a8fd40b7b3b111ec86d981 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 12:00:51 +0000 Subject: [PATCH 128/167] Bump System.Linq.Async from 5.0.0 to 5.1.0 Bumps [System.Linq.Async](https://github.com/dotnet/reactive) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/dotnet/reactive/releases) - [Commits](https://github.com/dotnet/reactive/compare/ixnet-v5.0.0...ixnet-v5.1.0) --- updated-dependencies: - dependency-name: System.Linq.Async dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- .../Jellyfin.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 73ee694245..8b461e6af2 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -18,7 +18,7 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="System.Linq.Async" Version="5.0.0" /> + <PackageReference Include="System.Linq.Async" Version="5.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0"> From 250332104b18bafee415ab1a69b2b355f9c18f6b Mon Sep 17 00:00:00 2001 From: Stoica Tedy <stoicatedy@gmail.com> Date: Tue, 14 Dec 2021 09:31:35 +0200 Subject: [PATCH 129/167] Fixed crash in MigrationRunner The crashed was caused by importing the migrationOptions even if the migrations.xml file is non existant. [Issue]: ~/.config/jellyfin/migrations.xml not found #6992 --- Jellyfin.Server/Migrations/MigrationRunner.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index a6886c64a6..825cf7338d 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -75,6 +75,10 @@ namespace Jellyfin.Server.Migrations var xmlSerializer = new MyXmlSerializer(); var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, MigrationsListStore.StoreKey.ToLowerInvariant() + ".xml"); + if (!File.Exists(migrationConfigPath)) + { + return; + } var migrationOptions = (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)!; // We have to deserialize it manually since the configuration manager may overwrite it From 4e0380193147ca1bce0e3d618de469fc0f63d031 Mon Sep 17 00:00:00 2001 From: Stoica Tedy <stoicatedy@gmail.com> Date: Tue, 14 Dec 2021 09:57:20 +0200 Subject: [PATCH 130/167] Update Jellyfin.Server/Migrations/MigrationRunner.cs Co-authored-by: Claus Vium <cvium@users.noreply.github.com> --- Jellyfin.Server/Migrations/MigrationRunner.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 825cf7338d..57e2804658 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -75,11 +75,9 @@ namespace Jellyfin.Server.Migrations var xmlSerializer = new MyXmlSerializer(); var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, MigrationsListStore.StoreKey.ToLowerInvariant() + ".xml"); - if (!File.Exists(migrationConfigPath)) - { - return; - } - var migrationOptions = (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)!; + var migrationOptions = File.Exists(migrationConfigPath) + ? (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)! + : new MigrationOptions(); // We have to deserialize it manually since the configuration manager may overwrite it var serverConfig = (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)!; From 0edf77994ac7f1890b22d828c54f91b8f4ca1486 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Tue, 14 Dec 2021 07:41:29 -0700 Subject: [PATCH 131/167] Cache BaseItemKind --- MediaBrowser.Controller/Entities/BaseItem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index b1ac2fe8ee..ce37c79373 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -40,6 +40,8 @@ namespace MediaBrowser.Controller.Entities /// </summary> public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem> { + private BaseItemKind? _baseItemKind; + /// <summary> /// The trailer folder name. /// </summary> @@ -2009,7 +2011,7 @@ namespace MediaBrowser.Controller.Entities public BaseItemKind GetBaseItemKind() { - return Enum.Parse<BaseItemKind>(GetClientTypeName()); + return _baseItemKind ??= Enum.Parse<BaseItemKind>(GetClientTypeName()); } /// <summary> From 0f4da9f635d18b48bd8d107021c83b2fdc945e73 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 14 Dec 2021 19:27:23 +0100 Subject: [PATCH 132/167] Fix crash on missing server config file --- Jellyfin.Server/Migrations/MigrationRunner.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 57e2804658..e9a45c140f 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -80,7 +80,10 @@ namespace Jellyfin.Server.Migrations : new MigrationOptions(); // We have to deserialize it manually since the configuration manager may overwrite it - var serverConfig = (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)!; + var serverConfig = File.Exists(appPaths.SystemConfigurationFilePath) + ? (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)! + : new ServerConfiguration(); + HandleStartupWizardCondition(migrations, migrationOptions, serverConfig.IsStartupWizardCompleted, logger); PerformMigrations(migrations, migrationOptions, options => xmlSerializer.SerializeToFile(options, migrationConfigPath), logger); } From e575d815cce8c18a2229c6bb9d981baed75dee52 Mon Sep 17 00:00:00 2001 From: Claus Vium <cvium@users.noreply.github.com> Date: Tue, 14 Dec 2021 22:57:01 +0100 Subject: [PATCH 133/167] Update MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs Co-authored-by: Joe Rogers <1337joe@users.noreply.github.com> --- MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 3809912ca4..efd02aef06 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -117,7 +117,7 @@ namespace MediaBrowser.LocalMetadata.Images return Enumerable.Empty<FileSystemMetadata>(); } - return directoryService.GetFiles(path) + return directoryService.GetFileSystemEntries(path) .Where(i => (includeDirectories && i.IsDirectory) || Array.FindIndex(BaseItem.SupportedImageExtensions, ext => string.Equals(ext, i.Extension, StringComparison.OrdinalIgnoreCase)) != -1) From c3c4dc6839d19cda8b0ea3cdcdc84547a713506d Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Tue, 14 Dec 2021 23:05:45 +0100 Subject: [PATCH 134/167] Review --- Emby.Naming/Video/StackResolver.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index 7900e09c2d..8119a02674 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -77,14 +77,14 @@ namespace Emby.Naming.Video var potentialStacks = new Dictionary<string, StackMetadata>(); foreach (var file in potentialFiles) { + var name = file.Name; + if (string.IsNullOrEmpty(name)) + { + name = Path.GetFileName(file.FullName); + } + for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++) { - var name = file.Name; - if (string.IsNullOrEmpty(name)) - { - name = Path.GetFileName(file.FullName); - } - var rule = namingOptions.VideoFileStackingRules[i]; if (!rule.Match(name, out var stackParsingResult)) { From 87439665d782a7a0f24d4015d53d584e66e40c2f Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Tue, 14 Dec 2021 15:10:40 -0700 Subject: [PATCH 135/167] Use array instead of HashSet --- .../Data/SqliteItemRepository.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 7731eb694f..48c3710cb5 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -196,7 +196,7 @@ namespace Emby.Server.Implementations.Data private static readonly string _mediaAttachmentInsertPrefix; - private static readonly HashSet<BaseItemKind> _programTypes = new() + private static readonly BaseItemKind[] _programTypes = new[] { BaseItemKind.Program, BaseItemKind.TvChannel, @@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.Data BaseItemKind.LiveTvChannel }; - private static readonly HashSet<BaseItemKind> _programExcludeParentTypes = new() + private static readonly BaseItemKind[] _programExcludeParentTypes = new[] { BaseItemKind.Series, BaseItemKind.Season, @@ -213,19 +213,19 @@ namespace Emby.Server.Implementations.Data BaseItemKind.PhotoAlbum }; - private static readonly HashSet<BaseItemKind> _serviceTypes = new() + private static readonly BaseItemKind[] _serviceTypes = new[] { BaseItemKind.TvChannel, BaseItemKind.LiveTvChannel }; - private static readonly HashSet<BaseItemKind> _startDateTypes = new() + private static readonly BaseItemKind[] _startDateTypes = new[] { BaseItemKind.Program, BaseItemKind.LiveTvProgram }; - private static readonly HashSet<BaseItemKind> _seriesTypes = new() + private static readonly BaseItemKind[] _seriesTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook, @@ -233,14 +233,14 @@ namespace Emby.Server.Implementations.Data BaseItemKind.Season }; - private static readonly HashSet<BaseItemKind> _artistExcludeParentTypes = new() + private static readonly BaseItemKind[] _artistExcludeParentTypes = new[] { BaseItemKind.Series, BaseItemKind.Season, BaseItemKind.PhotoAlbum }; - private static readonly HashSet<BaseItemKind> _artistsTypes = new() + private static readonly BaseItemKind[] _artistsTypes = new[] { BaseItemKind.Audio, BaseItemKind.MusicAlbum, From 543b0127b363d3fd371eb08166633ba6e0d2f9bd Mon Sep 17 00:00:00 2001 From: "Brian J. Murrell" <brian@interlinx.bc.ca> Date: Tue, 14 Dec 2021 12:52:48 -0500 Subject: [PATCH 136/167] Fix build on EL7 Add /usr/local/bin to $PATH. Update fedora/Makefile with enhancements from jellyfin-web. --- .copr | 1 + .copr/Makefile | 1 - fedora/Makefile | 82 ++++++++++++++++++++++++++------------------ fedora/jellyfin.spec | 1 + 4 files changed, 50 insertions(+), 35 deletions(-) create mode 120000 .copr delete mode 120000 .copr/Makefile diff --git a/.copr b/.copr new file mode 120000 index 0000000000..100fe0cd7b --- /dev/null +++ b/.copr @@ -0,0 +1 @@ +fedora \ No newline at end of file diff --git a/.copr/Makefile b/.copr/Makefile deleted file mode 120000 index ec3c90dfd9..0000000000 --- a/.copr/Makefile +++ /dev/null @@ -1 +0,0 @@ -../fedora/Makefile \ No newline at end of file diff --git a/fedora/Makefile b/fedora/Makefile index 6b09458b54..22cc30448d 100644 --- a/fedora/Makefile +++ b/fedora/Makefile @@ -1,41 +1,55 @@ -VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin.spec) -outdir ?= fedora/ +DIR := $(dir $(lastword $(MAKEFILE_LIST))) +INSTGIT := $(shell if [ "$$(id -u)" = "0" ]; then dnf -y install git; fi) +NAME := jellyfin-server +VERSION := $(shell sed -ne '/^Version:/s/.* *//p' $(DIR)/jellyfin.spec) +RELEASE := $(shell sed -ne '/^Release:/s/.* *\(.*\)%{.*}.*/\1/p' $(DIR)/jellyfin.spec) +GIT_VER := $(shell git describe --tags | sed -e 's/^v//' -e 's/-[0-9]*-g.*$$//') +SRPM := jellyfin-$(subst -,~,$(GIT_VER))-$(RELEASE)$(shell rpm --eval %dist).src.rpm +TARBALL :=$(NAME)-$(subst -,~,$(GIT_VER)).tar.gz + +epel-7-x86_64_repos := https://packages.microsoft.com/rhel/7/prod/ +epel-8-x86_64_repos := https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/ +fedora_repos := https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/ +fedora-34-x86_64_repos := $(fedora_repos) +fedora-35-x86_64_repos := $(fedora_repos) +fedora-34-x86_64_repos := $(fedora_repos) + +outdir ?= $(PWD)/$(DIR)/ TARGET ?= fedora-35-x86_64 -srpm: - pushd fedora/; \ - if [ "$$(id -u)" = "0" ]; then \ - dnf -y install git; \ - fi; \ - version=$$(git describe --tags | sed -e 's/^v//' \ - -e 's/-[0-9]*-g.*$$//' \ - -e 's/-/~/'); \ - SOURCE_DIR=.. \ - WORKDIR="$${PWD}"; \ - tar \ - --transform "s,^\.,jellyfin-server-$$version," \ - --exclude='.git*' \ - --exclude='**/.git' \ - --exclude='**/.hg' \ - --exclude='**/.vs' \ - --exclude='**/.vscode' \ - --exclude=deployment \ - --exclude='**/bin' \ - --exclude='**/obj' \ - --exclude='**/.nuget' \ - --exclude='*.deb' \ - --exclude='*.rpm' \ - --exclude=jellyfin-server-$$version.tar.gz \ - -czf "jellyfin-server-$$version.tar.gz" \ - -C $${SOURCE_DIR} ./; \ - popd; \ - ./bump_version $$version - cd fedora/; \ +srpm: $(DIR)/$(SRPM) +tarball: $(DIR)/$(TARBALL) + +$(DIR)/$(TARBALL): + cd $(DIR)/; \ + SOURCE_DIR=.. \ + WORKDIR="$${PWD}"; \ + version=$(GIT_VER); \ + tar \ + --transform "s,^\.,$(NAME)-$(subst -,~,$(GIT_VER))," \ + --exclude='.git*' \ + --exclude='**/.git' \ + --exclude='**/.hg' \ + --exclude='**/.vs' \ + --exclude='**/.vscode' \ + --exclude=deployment \ + --exclude='**/bin' \ + --exclude='**/obj' \ + --exclude='**/.nuget' \ + --exclude='*.deb' \ + --exclude='*.rpm' \ + --exclude=$(notdir $@) \ + -czf $(notdir $@) \ + -C $${SOURCE_DIR} ./ + +$(DIR)/$(SRPM): $(DIR)/$(TARBALL) $(DIR)/jellyfin.spec + ./bump_version $(GIT_VER) + cd $(DIR)/; \ rpmbuild -bs jellyfin.spec \ --define "_sourcedir $$PWD/" \ --define "_srcrpmdir $(outdir)" -rpms: fedora/jellyfin-$(shell git describe --tags | sed -e 's/^v//' -e 's/-[0-9]*-g.*$$//' -e 's/-/~/')-1$(shell rpm --eval %dist).src.rpm - mock --addrepo=https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/ \ - --enable-network \ +rpms: $(DIR)/$(SRPM) + mock $(addprefix --addrepo=, $($(TARGET)_repos)) \ + --enable-network \ -r $(TARGET) $< diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index a4584364ef..e93944a205 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -64,6 +64,7 @@ the Jellyfin server to bind to ports 80 and/or 443 for example. %install export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +export PATH=$PATH:/usr/local/bin dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \ "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE From 5e8aaa68cfd0435848a4571324ead22d402be771 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Tue, 14 Dec 2021 23:47:07 -0700 Subject: [PATCH 137/167] Update to dotnet 6.0.1 --- .../Emby.Server.Implementations.csproj | 2 +- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- .../Jellyfin.Server.Implementations.csproj | 8 ++++---- Jellyfin.Server/Jellyfin.Server.csproj | 4 ++-- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- deployment/Dockerfile.centos.amd64 | 2 +- deployment/Dockerfile.fedora.amd64 | 2 +- deployment/Dockerfile.ubuntu.amd64 | 2 +- deployment/Dockerfile.ubuntu.arm64 | 2 +- deployment/Dockerfile.ubuntu.armhf | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 13 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 95acd216d9..329a84acb9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,7 +29,7 @@ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" /> <PackageReference Include="Mono.Nat" Version="3.0.2" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" /> <PackageReference Include="sharpcompress" Version="0.30.1" /> diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index a3598edfa4..ccd647ebec 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -13,7 +13,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.0" /> + <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.2.3" /> diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 8b461e6af2..d22757c033 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -19,13 +19,13 @@ <ItemGroup> <PackageReference Include="System.Linq.Async" Version="5.1.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0"> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0"> + <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index bfe8e82e85..1638310fde 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -33,8 +33,8 @@ <PackageReference Include="CommandLineParser" Version="2.8.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> - <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0" /> - <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.0" /> + <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.1" /> + <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.1" /> <PackageReference Include="prometheus-net" Version="5.0.2" /> <PackageReference Include="prometheus-net.AspNetCore" Version="5.0.2" /> <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" /> diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b1fbe864b4..a161b99fd2 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -36,7 +36,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="System.Globalization" Version="4.3.0" /> - <PackageReference Include="System.Text.Json" Version="6.0.0" /> + <PackageReference Include="System.Text.Json" Version="6.0.1" /> </ItemGroup> <ItemGroup> diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64 index 3967a165d2..708c706b57 100644 --- a/deployment/Dockerfile.centos.amd64 +++ b/deployment/Dockerfile.centos.amd64 @@ -13,7 +13,7 @@ RUN yum update -yq \ && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget # Install DotNET SDK -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/17b6759f-1af0-41bc-ab12-209ba0377779/e8d02195dbf1434b940e0f05ae086453/dotnet-sdk-6.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64 index bc40a8059e..30615cd425 100644 --- a/deployment/Dockerfile.fedora.amd64 +++ b/deployment/Dockerfile.fedora.amd64 @@ -12,7 +12,7 @@ RUN dnf update -yq \ && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget # Install DotNET SDK -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/17b6759f-1af0-41bc-ab12-209ba0377779/e8d02195dbf1434b940e0f05ae086453/dotnet-sdk-6.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index c1b541c596..ccfaaa5f07 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -17,7 +17,7 @@ RUN apt-get update -yqq \ libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 # Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/17b6759f-1af0-41bc-ab12-209ba0377779/e8d02195dbf1434b940e0f05ae086453/dotnet-sdk-6.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index 6aa98a2890..988c8f16d9 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,7 +16,7 @@ RUN apt-get update -yqq \ mmv build-essential lsb-release # Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/17b6759f-1af0-41bc-ab12-209ba0377779/e8d02195dbf1434b940e0f05ae086453/dotnet-sdk-6.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index cc9d8dc797..61a008d6a1 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,7 +16,7 @@ RUN apt-get update -yqq \ mmv build-essential lsb-release # Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/17b6759f-1af0-41bc-ab12-209ba0377779/e8d02195dbf1434b940e0f05ae086453/dotnet-sdk-6.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ede8a287-3d61-4988-a356-32ff9129079e/bdb47b6b510ed0c4f0b132f7f4ad9d5a/dotnet-sdk-6.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 2aced06691..bcbe9c1be0 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,7 +15,7 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> <PackageReference Include="xunit" Version="2.4.1" /> diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 5b884cddf6..a59900b029 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,7 +9,7 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> <PackageReference Include="xunit" Version="2.4.1" /> diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 29d7646a66..ada9034dff 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,7 +10,7 @@ <PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> <PackageReference Include="xunit" Version="2.4.1" /> From e34fd15196837446f064575468da9e2272918a87 Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Tue, 14 Dec 2021 10:21:33 +0000 Subject: [PATCH 138/167] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 2e0fbc366c..deb28970c0 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -2,7 +2,7 @@ "Albums": "Album-album", "AppDeviceValues": "Apl: {0}, Peranti: {1}", "Application": "Aplikasi", - "Artists": "Artis", + "Artists": "Artis-artis", "AuthenticationSucceededWithUserName": "{0} berjaya disahkan", "Books": "Buku-buku", "CameraImageUploadedFrom": "Gambar baharu telah dimuat naik melalui {0}", @@ -39,7 +39,7 @@ "MixedContent": "Kandungan campuran", "Movies": "Filem-filem", "Music": "Muzik", - "MusicVideos": "", + "MusicVideos": "Video muzik", "NameInstallFailed": "{0} pemasangan gagal", "NameSeasonNumber": "Musim {0}", "NameSeasonUnknown": "Musim Tidak Diketahui", @@ -75,7 +75,7 @@ "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", - "Sync": "", + "Sync": "Segerak", "System": "Sistem", "TvShows": "Tayangan TV", "User": "Pengguna", From b5d4fdb56e59772b925d75cfe1c8ba691dbe1e77 Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Tue, 14 Dec 2021 18:04:51 +0000 Subject: [PATCH 139/167] Translated using Weblate (Welsh) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cy/ --- Emby.Server.Implementations/Localization/Core/cy.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/cy.json b/Emby.Server.Implementations/Localization/Core/cy.json index 0fa72dea41..c41176f0f2 100644 --- a/Emby.Server.Implementations/Localization/Core/cy.json +++ b/Emby.Server.Implementations/Localization/Core/cy.json @@ -10,5 +10,8 @@ "AuthenticationSucceededWithUserName": "{0} wedi’i ddilysu’n llwyddiannus", "Artists": "Artistiaid", "AppDeviceValues": "Ap: {0}, Dyfais: {1}", - "Albums": "Albwmau" + "Albums": "Albwmau", + "Genres": "Genres", + "Folders": "Ffolderi", + "Favorites": "Ffefrynnau" } From dea5a3f3bcf24c1ddfa8fa672a39cf1be136c36d Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Thu, 16 Dec 2021 00:37:01 +0100 Subject: [PATCH 140/167] Deprecate LibraryOptions.EnableInternetProviders --- .../BaseItemManager/BaseItemManager.cs | 10 ---------- MediaBrowser.Model/Configuration/LibraryOptions.cs | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index ba2f419a2e..d273b54fc6 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -55,11 +55,6 @@ namespace MediaBrowser.Controller.BaseItemManager return typeOptions.MetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); } - if (!libraryOptions.EnableInternetProviders) - { - return false; - } - var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, baseItem.GetType().Name, StringComparison.OrdinalIgnoreCase)); return itemConfig == null || !itemConfig.DisabledMetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); @@ -86,11 +81,6 @@ namespace MediaBrowser.Controller.BaseItemManager return typeOptions.ImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); } - if (!libraryOptions.EnableInternetProviders) - { - return false; - } - var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, baseItem.GetType().Name, StringComparison.OrdinalIgnoreCase)); return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index ef049af4b0..d3ce6aa7fb 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -21,7 +21,6 @@ namespace MediaBrowser.Model.Configuration SaveSubtitlesWithMedia = true; EnableRealtimeMonitor = true; PathInfos = Array.Empty<MediaPathInfo>(); - EnableInternetProviders = true; EnableAutomaticSeriesGrouping = true; SeasonZeroDisplayName = "Specials"; } @@ -38,6 +37,7 @@ namespace MediaBrowser.Model.Configuration public bool SaveLocalMetadata { get; set; } + [Obsolete("Disable remote providers in TypeOptions instead")] public bool EnableInternetProviders { get; set; } public bool EnableAutomaticSeriesGrouping { get; set; } From 249a1ca955a26f696bca28e6c755947ab670a3e6 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Wed, 15 Dec 2021 17:57:39 -0700 Subject: [PATCH 141/167] Add mapping from BaseItemKind to Type.FullName for querying --- .../Data/SqliteItemRepository.cs | 75 ++++++++++++++++--- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 48c3710cb5..8fe0b98d0c 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Data private readonly ItemFields[] _allItemFields = Enum.GetValues<ItemFields>(); - private static readonly string[] _retriveItemColumns = + private static readonly string[] _retrieveItemColumns = { "type", "data", @@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Data "OwnerId" }; - private static readonly string _retriveItemColumnsSelectQuery = $"select {string.Join(',', _retriveItemColumns)} from TypedBaseItems where guid = @guid"; + private static readonly string _retrieveItemColumnsSelectQuery = $"select {string.Join(',', _retrieveItemColumns)} from TypedBaseItems where guid = @guid"; private static readonly string[] _mediaStreamSaveColumns = { @@ -284,6 +284,43 @@ namespace Emby.Server.Implementations.Data private readonly Dictionary<string, string> _types = GetTypeMapDictionary(); + private static readonly Dictionary<BaseItemKind, string> _baseItemKindNames = new() + { + { BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName }, + { BaseItemKind.Audio, typeof(Audio).FullName }, + { BaseItemKind.AudioBook, typeof(AudioBook).FullName }, + { BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName }, + { BaseItemKind.Book, typeof(Book).FullName }, + { BaseItemKind.BoxSet, typeof(BoxSet).FullName }, + { BaseItemKind.Channel, typeof(Channel).FullName }, + { BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName }, + { BaseItemKind.Episode, typeof(Episode).FullName }, + { BaseItemKind.Folder, typeof(Folder).FullName }, + { BaseItemKind.Genre, typeof(Genre).FullName }, + { BaseItemKind.Movie, typeof(Movie).FullName }, + { BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName }, + { BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName }, + { BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName }, + { BaseItemKind.MusicArtist, typeof(MusicArtist).FullName }, + { BaseItemKind.MusicGenre, typeof(MusicGenre).FullName }, + { BaseItemKind.MusicVideo, typeof(MusicVideo).FullName }, + { BaseItemKind.Person, typeof(Person).FullName }, + { BaseItemKind.Photo, typeof(Photo).FullName }, + { BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName }, + { BaseItemKind.Playlist, typeof(Playlist).FullName }, + { BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName }, + { BaseItemKind.Season, typeof(Season).FullName }, + { BaseItemKind.Series, typeof(Series).FullName }, + { BaseItemKind.Studio, typeof(Studio).FullName }, + { BaseItemKind.Trailer, typeof(Trailer).FullName }, + { BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName }, + { BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName }, + { BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName }, + { BaseItemKind.UserView, typeof(UserView).FullName }, + { BaseItemKind.Video, typeof(Video).FullName }, + { BaseItemKind.Year, typeof(Year).FullName } + }; + static SqliteItemRepository() { var queryPrefixText = new StringBuilder(); @@ -1319,7 +1356,7 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection(true)) { - using (var statement = PrepareStatement(connection, _retriveItemColumnsSelectQuery)) + using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery)) { statement.TryBind("@guid", id); @@ -2629,7 +2666,7 @@ namespace Emby.Server.Implementations.Data query.Limit = query.Limit.Value + 4; } - var columns = _retriveItemColumns.ToList(); + var columns = _retrieveItemColumns.ToList(); SetFinalColumnsToSelect(query, columns); var commandTextBuilder = new StringBuilder("select ", 1024) .AppendJoin(',', columns) @@ -2820,7 +2857,7 @@ namespace Emby.Server.Implementations.Data query.Limit = query.Limit.Value + 4; } - var columns = _retriveItemColumns.ToList(); + var columns = _retrieveItemColumns.ToList(); SetFinalColumnsToSelect(query, columns); var commandTextBuilder = new StringBuilder("select ", 512) .AppendJoin(',', columns) @@ -3569,23 +3606,37 @@ namespace Emby.Server.Implementations.Data var excludeTypes = query.ExcludeItemTypes; if (excludeTypes.Length == 1) { - whereClauses.Add("type<>@type"); - statement?.TryBind("@type", excludeTypes[0].ToString()); + if (_baseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName)) + { + whereClauses.Add("type<>@type"); + statement?.TryBind("@type", excludeTypeName); + } } else if (excludeTypes.Length > 1) { - var inClause = string.Join(',', excludeTypes.Select(i => "'" + i + "'")); + var inClause = string.Join( + ',', + excludeTypes + .Select(i => _baseItemKindNames.TryGetValue(i, out var baseItemKindName) ? "'" + baseItemKindName + "'" : null) + .Where(i => !string.IsNullOrEmpty(i))); whereClauses.Add($"type not in ({inClause})"); } } else if (includeTypes.Length == 1) { - whereClauses.Add("type=@type"); - statement?.TryBind("@type", includeTypes[0].ToString()); + if (_baseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName)) + { + whereClauses.Add("type=@type"); + statement?.TryBind("@type", includeTypeName); + } } else if (includeTypes.Length > 1) { - var inClause = string.Join(',', includeTypes.Select(i => "'" + i + "'")); + var inClause = string.Join( + ',', + includeTypes + .Select(i => _baseItemKindNames.TryGetValue(i, out var baseItemKindName) ? "'" + baseItemKindName + "'" : null) + .Where(i => !string.IsNullOrEmpty(i))); whereClauses.Add($"type in ({inClause})"); } @@ -5334,7 +5385,7 @@ AND Type = @InternalPersonType)"); stringBuilder.Clear(); } - List<string> columns = _retriveItemColumns.ToList(); + List<string> columns = _retrieveItemColumns.ToList(); // Unfortunately we need to add it to columns to ensure the order of the columns in the select if (!string.IsNullOrEmpty(itemCountColumns)) { From 0dd304f86c65fe38a5d74186a7b6a5262d46cd50 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Thu, 16 Dec 2021 07:54:22 -0700 Subject: [PATCH 142/167] Log warning for unknown BaseItemKind to Type mapping --- .../Data/SqliteItemRepository.cs | 58 +++++++++++++++---- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 8fe0b98d0c..312f16b253 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3611,15 +3611,32 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("type<>@type"); statement?.TryBind("@type", excludeTypeName); } + else + { + Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeTypes[0]); + } } else if (excludeTypes.Length > 1) { - var inClause = string.Join( - ',', - excludeTypes - .Select(i => _baseItemKindNames.TryGetValue(i, out var baseItemKindName) ? "'" + baseItemKindName + "'" : null) - .Where(i => !string.IsNullOrEmpty(i))); - whereClauses.Add($"type not in ({inClause})"); + var whereBuilder = new StringBuilder(); + foreach (var excludeType in excludeTypes) + { + if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName)) + { + whereBuilder + .Append('\'') + .Append(baseItemKindName) + .Append("',"); + } + else + { + Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeType); + } + } + + // Remove trailing comma. + whereBuilder.Length--; + whereClauses.Add($"type not in ({whereBuilder})"); } } else if (includeTypes.Length == 1) @@ -3629,15 +3646,32 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("type=@type"); statement?.TryBind("@type", includeTypeName); } + else + { + Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeTypes[0]); + } } else if (includeTypes.Length > 1) { - var inClause = string.Join( - ',', - includeTypes - .Select(i => _baseItemKindNames.TryGetValue(i, out var baseItemKindName) ? "'" + baseItemKindName + "'" : null) - .Where(i => !string.IsNullOrEmpty(i))); - whereClauses.Add($"type in ({inClause})"); + var whereBuilder = new StringBuilder(); + foreach (var includeType in includeTypes) + { + if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName)) + { + whereBuilder + .Append('\'') + .Append(baseItemKindName) + .Append("',"); + } + else + { + Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeType); + } + } + + // Remove trailing comma. + whereBuilder.Length--; + whereClauses.Add($"type in ({whereBuilder})"); } if (query.ChannelIds.Count == 1) From 8ddba552142921e243dc74a3ea594dfbf9d65677 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Thu, 16 Dec 2021 09:18:51 -0700 Subject: [PATCH 143/167] Use string builder instead of string interpolation --- .../Data/SqliteItemRepository.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 312f16b253..beae7e2431 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3618,7 +3618,7 @@ namespace Emby.Server.Implementations.Data } else if (excludeTypes.Length > 1) { - var whereBuilder = new StringBuilder(); + var whereBuilder = new StringBuilder("type not in ("); foreach (var excludeType in excludeTypes) { if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName)) @@ -3636,7 +3636,8 @@ namespace Emby.Server.Implementations.Data // Remove trailing comma. whereBuilder.Length--; - whereClauses.Add($"type not in ({whereBuilder})"); + whereBuilder.Append(')'); + whereClauses.Add(whereBuilder.ToString()); } } else if (includeTypes.Length == 1) @@ -3653,7 +3654,7 @@ namespace Emby.Server.Implementations.Data } else if (includeTypes.Length > 1) { - var whereBuilder = new StringBuilder(); + var whereBuilder = new StringBuilder("type in ("); foreach (var includeType in includeTypes) { if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName)) @@ -3671,7 +3672,8 @@ namespace Emby.Server.Implementations.Data // Remove trailing comma. whereBuilder.Length--; - whereClauses.Add($"type in ({whereBuilder})"); + whereBuilder.Append(')'); + whereClauses.Add(whereBuilder.ToString()); } if (query.ChannelIds.Count == 1) From 92448ffabd3236b6637492f0937d252d9d35d0ad Mon Sep 17 00:00:00 2001 From: nlog <sorisem4106@naver.com> Date: Sat, 18 Dec 2021 13:00:51 +0900 Subject: [PATCH 144/167] Remove ProtectClock for hardware encoding --- debian/jellyfin.service | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/jellyfin.service b/debian/jellyfin.service index 071f949dd9..b86f40473a 100644 --- a/debian/jellyfin.service +++ b/debian/jellyfin.service @@ -16,7 +16,6 @@ RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK RestrictNamespaces=true RestrictRealtime=true RestrictSUIDSGID=true -ProtectClock=true ProtectControlGroups=true ProtectHostname=true ProtectKernelLogs=true From f8fcbc88fca8086052d1b3ef37dbd71d85e7dee7 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Sun, 12 Dec 2021 02:22:30 +0100 Subject: [PATCH 145/167] Add tests for ProbeResultNormalizer.GetFrameRate --- .../Probing/ProbeResultNormalizer.cs | 31 +++++++++++-------- .../Probing/ProbeResultNormalizerTests.cs | 13 ++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 770881149f..ed0c8a2e10 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1027,27 +1027,32 @@ namespace MediaBrowser.MediaEncoding.Probing /// </summary> /// <param name="value">The value.</param> /// <returns>System.Nullable{System.Single}.</returns> - private float? GetFrameRate(string value) + internal static float? GetFrameRate(ReadOnlySpan<char> value) { - if (string.IsNullOrEmpty(value)) + if (value.IsEmpty) { return null; } - var parts = value.Split('/'); - - float result; - - if (parts.Length == 2) + int index = value.IndexOf('/'); + if (index == -1) { - result = float.Parse(parts[0], CultureInfo.InvariantCulture) / float.Parse(parts[1], CultureInfo.InvariantCulture); - } - else - { - result = float.Parse(parts[0], CultureInfo.InvariantCulture); + // REVIEW: is this branch actually required? (i.e. does ffprobe ever output something other than a fraction?) + if (float.TryParse(value, NumberStyles.AllowThousands | NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) + { + return result; + } + + return null; } - return float.IsNaN(result) ? null : result; + if (!float.TryParse(value[..index], NumberStyles.Integer, CultureInfo.InvariantCulture, out var dividend) + || !float.TryParse(value[(index + 1)..], NumberStyles.Integer, CultureInfo.InvariantCulture, out var divisor)) + { + return null; + } + + return divisor == 0f ? 0f : dividend / divisor; } private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data) diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 4504924cbf..cc1ec495a0 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -18,6 +18,19 @@ namespace Jellyfin.MediaEncoding.Tests.Probing private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly ProbeResultNormalizer _probeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), null); + [Theory] + [InlineData("2997/125", 23.976f)] + [InlineData("1/50", 0.02f)] + [InlineData("25/1", 25f)] + [InlineData("120/1", 120f)] + [InlineData("1704753000/71073479", 23.98578237601117f)] + [InlineData("0/0", 0f)] + [InlineData("1/1000", 0.001f)] + [InlineData("1/90000", 1.1111111E-05f)] + [InlineData("1/48000", 2.0833333E-05f)] + public void GetFrameRate_Success(string value, float? expected) + => Assert.Equal(expected, ProbeResultNormalizer.GetFrameRate(value)); + [Fact] public void GetMediaInfo_MetaData_Success() { From 968c534864efbd979feedb859b4b2afeb279ae52 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Wed, 15 Dec 2021 18:33:27 +0100 Subject: [PATCH 146/167] Return null on division by zero --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 2 +- .../Probing/ProbeResultNormalizerTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index ed0c8a2e10..da76ff0f98 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1052,7 +1052,7 @@ namespace MediaBrowser.MediaEncoding.Probing return null; } - return divisor == 0f ? 0f : dividend / divisor; + return divisor == 0f ? null : dividend / divisor; } private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data) diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index cc1ec495a0..0fc8724b6a 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -24,7 +24,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing [InlineData("25/1", 25f)] [InlineData("120/1", 120f)] [InlineData("1704753000/71073479", 23.98578237601117f)] - [InlineData("0/0", 0f)] + [InlineData("0/0", null)] [InlineData("1/1000", 0.001f)] [InlineData("1/90000", 1.1111111E-05f)] [InlineData("1/48000", 2.0833333E-05f)] From 55146334e870b6bc32a1e2ff8cc9e74d94e12d1f Mon Sep 17 00:00:00 2001 From: Oatavandi <oatavandi@gmail.com> Date: Fri, 17 Dec 2021 16:03:19 +0000 Subject: [PATCH 147/167] Translated using Weblate (Tamil) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ta/ --- Emby.Server.Implementations/Localization/Core/ta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index e3a993625d..98d763fcd6 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -21,7 +21,7 @@ "Inherit": "மரபுரிமையாகப் பெறு", "HeaderRecordingGroups": "பதிவு குழுக்கள்", "Folders": "கோப்புறைகள்", - "FailedLoginAttemptWithUserName": "{0} இல் இருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது", + "FailedLoginAttemptWithUserName": "{0} இன் உள்நுழைவு முயற்சி தோல்வியடைந்தது", "DeviceOnlineWithName": "{0} இணைக்கப்பட்டது", "DeviceOfflineWithName": "{0} துண்டிக்கப்பட்டது", "Collections": "தொகுப்புகள்", From c35b704a839ca7296998ebf4c40eff01d94e307e Mon Sep 17 00:00:00 2001 From: WWWesten <wwwesten@gmail.com> Date: Thu, 16 Dec 2021 19:32:13 +0000 Subject: [PATCH 148/167] Translated using Weblate (Welsh) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cy/ --- .../Localization/Core/cy.json | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/cy.json b/Emby.Server.Implementations/Localization/Core/cy.json index c41176f0f2..1192f5c88a 100644 --- a/Emby.Server.Implementations/Localization/Core/cy.json +++ b/Emby.Server.Implementations/Localization/Core/cy.json @@ -13,5 +13,46 @@ "Albums": "Albwmau", "Genres": "Genres", "Folders": "Ffolderi", - "Favorites": "Ffefrynnau" + "Favorites": "Ffefrynnau", + "LabelRunningTimeValue": "Amser rhedeg: {0}", + "TaskOptimizeDatabase": "Cronfa ddata Optimeiddio", + "TaskRefreshChannels": "Adnewyddu Sianeli", + "TaskRefreshPeople": "Adnewyddu Pobl", + "TasksChannelsCategory": "Sianeli Internet", + "VersionNumber": "Fersiwn {0}", + "ScheduledTaskStartedWithName": "{0} wedi dechrau", + "ScheduledTaskFailedWithName": "{0} wedi methu", + "ProviderValue": "Darparwr: {0}", + "NotificationOptionInstallationFailed": "Fethu Gosod", + "NameSeasonUnknown": "Tymor Anhysbys", + "NameSeasonNumber": "Tymor {0}", + "MusicVideos": "Fideos Cerddoriaeth", + "MixedContent": "Cynnwys amrywiol", + "HomeVideos": "Fideos Cartref", + "HeaderNextUp": "Nesaf i Fyny", + "HeaderFavoriteArtists": "Ffefryn Artistiaid", + "HeaderFavoriteAlbums": "Ffefryn Albwmau", + "HeaderContinueWatching": "Parhewch i Weithio", + "TasksApplicationCategory": "Rhaglen", + "TasksLibraryCategory": "Llyfrgell", + "TasksMaintenanceCategory": "Cynnal a Chadw", + "System": "System", + "Plugin": "Ategyn", + "Music": "Cerddoriaeth", + "Latest": "Diweddaraf", + "Inherit": "Etifeddu", + "Forced": "Orfodi", + "Application": "Rhaglen", + "HeaderAlbumArtists": "Artistiaid albwm", + "Sync": "Cysoni", + "Songs": "Caneuon", + "Shows": "Rhaglenni", + "Playlists": "Rhestri Chwarae", + "Photos": "Lluniau", + "ValueSpecialEpisodeName": "Arbennig - {0}", + "Movies": "Ffilmiau", + "Undefined": "Heb ddiffiniad", + "TvShows": "Rhaglenni teledu", + "HeaderLiveTV": "Teledu Byw", + "User": "Defnyddiwr" } From a4565da4a998cfaa5bb8ff9d96d3d62b25574a90 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Sat, 18 Dec 2021 17:49:33 +0100 Subject: [PATCH 149/167] Use System.IO.Compression instead of SharpCompress for zips Also removes unused methods from ZipClient --- .../Archiving/ZipClient.cs | 116 ------------------ .../Updates/InstallationManager.cs | 8 +- MediaBrowser.Model/IO/IZipClient.cs | 56 --------- .../Updates/InstallationManagerTests.cs | 1 - 4 files changed, 3 insertions(+), 178 deletions(-) diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs index 9e1d550ebc..6a3b250d25 100644 --- a/Emby.Server.Implementations/Archiving/ZipClient.cs +++ b/Emby.Server.Implementations/Archiving/ZipClient.cs @@ -1,11 +1,8 @@ using System.IO; using MediaBrowser.Model.IO; -using SharpCompress.Archives.SevenZip; -using SharpCompress.Archives.Tar; using SharpCompress.Common; using SharpCompress.Readers; using SharpCompress.Readers.GZip; -using SharpCompress.Readers.Zip; namespace Emby.Server.Implementations.Archiving { @@ -14,55 +11,6 @@ namespace Emby.Server.Implementations.Archiving /// </summary> public class ZipClient : IZipClient { - /// <summary> - /// Extracts all. - /// </summary> - /// <param name="sourceFile">The source file.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles) - { - using var fileStream = File.OpenRead(sourceFile); - ExtractAll(fileStream, targetPath, overwriteExistingFiles); - } - - /// <summary> - /// Extracts all. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles) - { - using var reader = ReaderFactory.Open(source); - var options = new ExtractionOptions - { - ExtractFullPath = true - }; - - if (overwriteExistingFiles) - { - options.Overwrite = true; - } - - Directory.CreateDirectory(targetPath); - reader.WriteAllToDirectory(targetPath, options); - } - - /// <inheritdoc /> - public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles) - { - using var reader = ZipReader.Open(source); - var options = new ExtractionOptions - { - ExtractFullPath = true, - Overwrite = overwriteExistingFiles - }; - - Directory.CreateDirectory(targetPath); - reader.WriteAllToDirectory(targetPath, options); - } - /// <inheritdoc /> public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles) { @@ -94,69 +42,5 @@ namespace Emby.Server.Implementations.Archiving reader.WriteEntryToFile(Path.Combine(targetPath, filename)); } } - - /// <summary> - /// Extracts all from7z. - /// </summary> - /// <param name="sourceFile">The source file.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles) - { - using var fileStream = File.OpenRead(sourceFile); - ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles); - } - - /// <summary> - /// Extracts all from7z. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles) - { - using var archive = SevenZipArchive.Open(source); - using var reader = archive.ExtractAllEntries(); - var options = new ExtractionOptions - { - ExtractFullPath = true, - Overwrite = overwriteExistingFiles - }; - - Directory.CreateDirectory(targetPath); - reader.WriteAllToDirectory(targetPath, options); - } - - /// <summary> - /// Extracts all from tar. - /// </summary> - /// <param name="sourceFile">The source file.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles) - { - using var fileStream = File.OpenRead(sourceFile); - ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles); - } - - /// <summary> - /// Extracts all from tar. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles) - { - using var archive = TarArchive.Open(source); - using var reader = archive.ExtractAllEntries(); - var options = new ExtractionOptions - { - ExtractFullPath = true, - Overwrite = overwriteExistingFiles - }; - - Directory.CreateDirectory(targetPath); - reader.WriteAllToDirectory(targetPath, options); - } } } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index ef95ebf943..7f7eec7d93 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Net.Http.Json; @@ -47,7 +48,6 @@ namespace Emby.Server.Implementations.Updates /// </summary> /// <value>The application host.</value> private readonly IServerApplicationHost _applicationHost; - private readonly IZipClient _zipClient; private readonly object _currentInstallationsLock = new object(); /// <summary> @@ -69,7 +69,6 @@ namespace Emby.Server.Implementations.Updates /// <param name="eventManager">The <see cref="IEventManager"/>.</param> /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> /// <param name="config">The <see cref="IServerConfigurationManager"/>.</param> - /// <param name="zipClient">The <see cref="IZipClient"/>.</param> /// <param name="pluginManager">The <see cref="IPluginManager"/>.</param> public InstallationManager( ILogger<InstallationManager> logger, @@ -78,7 +77,6 @@ namespace Emby.Server.Implementations.Updates IEventManager eventManager, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, - IZipClient zipClient, IPluginManager pluginManager) { _currentInstallations = new List<(InstallationInfo, CancellationTokenSource)>(); @@ -90,7 +88,6 @@ namespace Emby.Server.Implementations.Updates _eventManager = eventManager; _httpClientFactory = httpClientFactory; _config = config; - _zipClient = zipClient; _jsonSerializerOptions = JsonDefaults.Options; _pluginManager = pluginManager; } @@ -560,7 +557,8 @@ namespace Emby.Server.Implementations.Updates } stream.Position = 0; - _zipClient.ExtractAllFromZip(stream, targetDir, true); + using var reader = new ZipArchive(stream); + reader.ExtractToDirectory(targetDir, true); await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false); _pluginManager.ImportPluginFrom(targetDir); } diff --git a/MediaBrowser.Model/IO/IZipClient.cs b/MediaBrowser.Model/IO/IZipClient.cs index fca52ebae6..2448575d19 100644 --- a/MediaBrowser.Model/IO/IZipClient.cs +++ b/MediaBrowser.Model/IO/IZipClient.cs @@ -9,64 +9,8 @@ namespace MediaBrowser.Model.IO /// </summary> public interface IZipClient { - /// <summary> - /// Extracts all. - /// </summary> - /// <param name="sourceFile">The source file.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles); - - /// <summary> - /// Extracts all. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles); - void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles); void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName); - - /// <summary> - /// Extracts all from zip. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles); - - /// <summary> - /// Extracts all from7z. - /// </summary> - /// <param name="sourceFile">The source file.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles); - - /// <summary> - /// Extracts all from7z. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles); - - /// <summary> - /// Extracts all from tar. - /// </summary> - /// <param name="sourceFile">The source file.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles); - - /// <summary> - /// Extracts all from tar. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="targetPath">The target path.</param> - /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> - void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles); } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs index 09c4bd1004..d18441ac01 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs @@ -44,7 +44,6 @@ namespace Jellyfin.Server.Implementations.Tests.Updates ConfigureMembers = true }); _fixture.Inject(http); - _fixture.Inject<IZipClient>(new ZipClient()); _installationManager = _fixture.Create<InstallationManager>(); } From 077f13ae4c949c11d06734d8fc3d44b6411edcc8 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sat, 18 Dec 2021 16:33:58 +0100 Subject: [PATCH 150/167] Increment library number instead of appending --- Emby.Server.Implementations/Library/LibraryManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 8cc9a2fd5e..411d9675f1 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2890,11 +2890,12 @@ namespace Emby.Server.Implementations.Library var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; + var existingNameCount = 1; // first numbered name will be 2 var virtualFolderPath = Path.Combine(rootFolderPath, name); while (Directory.Exists(virtualFolderPath)) { - name += "1"; - virtualFolderPath = Path.Combine(rootFolderPath, name); + existingNameCount++; + virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount); } var mediaPathInfos = options.PathInfos; From d9bc65469f21c1a4fbc358015e4d25d999bfb73c Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Sat, 18 Dec 2021 17:17:03 -0700 Subject: [PATCH 151/167] Fix query param spelling --- Jellyfin.Api/Controllers/TvShowsController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index f47e71bd17..e20bcd7a78 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -61,7 +61,7 @@ namespace Jellyfin.Api.Controllers /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> /// <param name="seriesId">Optional. Filter by series id.</param> /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param> - /// <param name="enableImges">Optional. Include image information in output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> /// <param name="enableUserData">Optional. Include user data.</param> @@ -78,7 +78,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] string? seriesId, [FromQuery] Guid? parentId, - [FromQuery] bool? enableImges, + [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, @@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers { var options = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var result = _tvSeriesManager.GetNextUp( new NextUpQuery @@ -125,7 +125,7 @@ namespace Jellyfin.Api.Controllers /// <param name="limit">Optional. The maximum number of records to return.</param> /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param> /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param> - /// <param name="enableImges">Optional. Include image information in output.</param> + /// <param name="enableImages">Optional. Include image information in output.</param> /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param> /// <param name="enableImageTypes">Optional. The image types to include in the output.</param> /// <param name="enableUserData">Optional. Include user data.</param> @@ -138,7 +138,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] Guid? parentId, - [FromQuery] bool? enableImges, + [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData) @@ -153,7 +153,7 @@ namespace Jellyfin.Api.Controllers var options = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { From ea9fc9f9cc3c269f55768882c631e8022ccb232d Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Sun, 19 Dec 2021 02:17:32 +0100 Subject: [PATCH 152/167] Remove unreachable branches from JsonConverters * If the type is a reference type we don't have to handle null ourselves * reader.ValueSpan is only valid if reader.HasValueSequence is false --- .../Converters/JsonDelimitedArrayConverter.cs | 8 ++++---- .../Json/Converters/JsonGuidConverter.cs | 18 ++++++++++------- .../Converters/JsonNullableGuidConverter.cs | 11 ++++------ .../Converters/JsonNullableStructConverter.cs | 20 ++++--------------- .../Json/Converters/JsonStringConverter.cs | 13 ++---------- 5 files changed, 25 insertions(+), 45 deletions(-) diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs index 51b955145f..321cfa502a 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -9,7 +9,7 @@ namespace Jellyfin.Extensions.Json.Converters /// Convert delimited string to array of type. /// </summary> /// <typeparam name="T">Type to convert to.</typeparam> - public abstract class JsonDelimitedArrayConverter<T> : JsonConverter<T[]?> + public abstract class JsonDelimitedArrayConverter<T> : JsonConverter<T[]> { private readonly TypeConverter _typeConverter; @@ -31,9 +31,9 @@ namespace Jellyfin.Extensions.Json.Converters { if (reader.TokenType == JsonTokenType.String) { - // GetString can't return null here because we already handled it above - var stringEntries = reader.GetString()?.Split(Delimiter, StringSplitOptions.RemoveEmptyEntries); - if (stringEntries == null || stringEntries.Length == 0) + // null got handled higher up the call stack + var stringEntries = reader.GetString()!.Split(Delimiter, StringSplitOptions.RemoveEmptyEntries); + if (stringEntries.Length == 0) { return Array.Empty<T>(); } diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs index be94dd5192..ea6d141cb2 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs @@ -12,15 +12,19 @@ namespace Jellyfin.Extensions.Json.Converters { /// <inheritdoc /> public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var guidStr = reader.GetString(); - return guidStr == null ? Guid.Empty : new Guid(guidStr); - } + => reader.TokenType == JsonTokenType.Null + ? Guid.Empty + : ReadInternal(ref reader); + + // TODO: optimize by parsing the UTF8 bytes instead of converting to string first + internal static Guid ReadInternal(ref Utf8JsonReader reader) + => Guid.Parse(reader.GetString()!); // null got handled higher up the call stack /// <inheritdoc /> public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString("N", CultureInfo.InvariantCulture)); - } + => WriteInternal(writer, value); + + internal static void WriteInternal(Utf8JsonWriter writer, Guid value) + => writer.WriteStringValue(value.ToString("N", CultureInfo.InvariantCulture)); } } diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs index 6192d1598d..b477bcb667 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; @@ -12,21 +11,19 @@ namespace Jellyfin.Extensions.Json.Converters { /// <inheritdoc /> public override Guid? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var guidStr = reader.GetString(); - return guidStr == null ? null : new Guid(guidStr); - } + => JsonGuidConverter.ReadInternal(ref reader); /// <inheritdoc /> public override void Write(Utf8JsonWriter writer, Guid? value, JsonSerializerOptions options) { - if (value == null || value == Guid.Empty) + if (value == Guid.Empty) { writer.WriteNullValue(); } else { - writer.WriteStringValue(value.Value.ToString("N", CultureInfo.InvariantCulture)); + // null got handled higher up the call stack + JsonGuidConverter.WriteInternal(writer, value!.Value); } } } diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs index 6de238b39e..28437023fe 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs @@ -15,13 +15,10 @@ namespace Jellyfin.Extensions.Json.Converters /// <inheritdoc /> public override TStruct? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.Null) - { - return null; - } - // Token is empty string. - if (reader.TokenType == JsonTokenType.String && ((reader.HasValueSequence && reader.ValueSequence.IsEmpty) || reader.ValueSpan.IsEmpty)) + if (reader.TokenType == JsonTokenType.String + && ((reader.HasValueSequence && reader.ValueSequence.IsEmpty) + || (!reader.HasValueSequence && reader.ValueSpan.IsEmpty))) { return null; } @@ -31,15 +28,6 @@ namespace Jellyfin.Extensions.Json.Converters /// <inheritdoc /> public override void Write(Utf8JsonWriter writer, TStruct? value, JsonSerializerOptions options) - { - if (value.HasValue) - { - JsonSerializer.Serialize(writer, value.Value, options); - } - else - { - writer.WriteNullValue(); - } - } + => JsonSerializer.Serialize(writer, value!.Value, options); // null got handled higher up the call stack } } diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs index 1a7a8c4f55..36b36c9b46 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs @@ -13,20 +13,11 @@ namespace Jellyfin.Extensions.Json.Converters { /// <inheritdoc /> public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.TokenType switch - { - JsonTokenType.Null => null, - JsonTokenType.String => reader.GetString(), - _ => GetRawValue(reader) - }; - } + => reader.TokenType == JsonTokenType.String ? reader.GetString() : GetRawValue(reader); /// <inheritdoc /> public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) - { - writer.WriteStringValue(value); - } + => writer.WriteStringValue(value); private static string GetRawValue(Utf8JsonReader reader) { From 76c2775d8c9b6eebe9638ba3b388746cb968e922 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Sun, 19 Dec 2021 10:27:57 +0100 Subject: [PATCH 153/167] Use static lambdas --- .../AppBase/BaseConfigurationManager.cs | 2 +- .../Localization/LocalizationManager.cs | 2 +- Emby.Server.Implementations/Serialization/MyXmlSerializer.cs | 2 +- MediaBrowser.Controller/Providers/DirectoryService.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index d385356345..5ba4749a60 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.AppBase { return _configurations.GetOrAdd( key, - (k, configurationManager) => + static (k, configurationManager) => { var file = configurationManager.GetConfigurationFile(k); diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 9cdbbb6a3e..dbd70342a5 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -310,7 +310,7 @@ namespace Emby.Server.Implementations.Localization return _dictionaries.GetOrAdd( culture, - (key, localizationManager) => localizationManager.GetDictionary(Prefix, key, DefaultCulture + ".json").GetAwaiter().GetResult(), + static (key, localizationManager) => localizationManager.GetDictionary(Prefix, key, DefaultCulture + ".json").GetAwaiter().GetResult(), this); } diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs index 059211a0b9..1bac2600ca 100644 --- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Serialization private static XmlSerializer GetSerializer(Type type) => _serializers.GetOrAdd( type.FullName ?? throw new ArgumentException($"Invalid type {type}."), - (_, t) => new XmlSerializer(t), + static (_, t) => new XmlSerializer(t), type); /// <summary> diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index b312702704..e6d975ffec 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Providers public FileSystemMetadata[] GetFileSystemEntries(string path) { - return _cache.GetOrAdd(path, (p, fileSystem) => fileSystem.GetFileSystemEntries(p).ToArray(), _fileSystem); + return _cache.GetOrAdd(path, static (p, fileSystem) => fileSystem.GetFileSystemEntries(p).ToArray(), _fileSystem); } public List<FileSystemMetadata> GetFiles(string path) @@ -69,7 +69,7 @@ namespace MediaBrowser.Controller.Providers _filePathCache.TryRemove(path, out _); } - var filePaths = _filePathCache.GetOrAdd(path, (p, fileSystem) => fileSystem.GetFilePaths(p).ToList(), _fileSystem); + var filePaths = _filePathCache.GetOrAdd(path, static (p, fileSystem) => fileSystem.GetFilePaths(p).ToList(), _fileSystem); if (sort) { From 91f3ce31096070fa21409aedcbd45e2a530833fe Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Sun, 19 Dec 2021 18:24:05 +0100 Subject: [PATCH 154/167] Use == instead of Object.Equals to avoid closure allocation --- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 4114ff7b0b..82d11c523e 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2739,7 +2739,7 @@ namespace MediaBrowser.Controller.Entities } /// <inheritdoc /> - public bool Equals(BaseItem other) => Equals(Id, other?.Id); + public bool Equals(BaseItem other) => Id == other?.Id; /// <inheritdoc /> public override int GetHashCode() => HashCode.Combine(Id); From 83a94aa612f2451acc1e9ce7fcfc5c88b7989396 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Mon, 20 Dec 2021 12:15:20 +0100 Subject: [PATCH 155/167] Fix extras folders --- Emby.Naming/Common/NamingOptions.cs | 29 ++++- Emby.Naming/Video/VideoListResolver.cs | 7 +- .../Library/CoreResolutionIgnoreRule.cs | 18 +-- .../Library/LibraryManager.cs | 67 ++++++++-- .../Library/Resolvers/Movies/MovieResolver.cs | 7 +- MediaBrowser.Controller/Entities/BaseItem.cs | 93 +------------- .../Library/ILibraryManager.cs | 11 +- .../Images/LocalImageProvider.cs | 3 +- .../Video/MultiVersionTests.cs | 104 +++------------ .../Video/VideoListResolverTests.cs | 120 +++--------------- .../Library/LibraryManager/FindExtrasTests.cs | 78 ++++++++++-- 11 files changed, 210 insertions(+), 327 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index b97e9d7637..aa62a47f17 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -1,6 +1,7 @@ #pragma warning disable CA1819 using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Video; @@ -475,6 +476,12 @@ namespace Emby.Naming.Common "theme", MediaType.Audio), + new ExtraRule( + ExtraType.ThemeSong, + ExtraRuleType.DirectoryName, + "theme-music", + MediaType.Audio), + new ExtraRule( ExtraType.Scene, ExtraRuleType.Suffix, @@ -569,7 +576,7 @@ namespace Emby.Naming.Common ExtraType.Unknown, ExtraRuleType.DirectoryName, "extras", - MediaType.Video), + MediaType.Video) }; Format3DRules = new[] @@ -681,9 +688,29 @@ namespace Emby.Naming.Common .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); + AllExtrasTypesFolderNames = new Dictionary<string, ExtraType>(StringComparer.OrdinalIgnoreCase) + { + ["trailers"] = ExtraType.Trailer, + ["theme-music"] = ExtraType.ThemeSong, + ["backdrops"] = ExtraType.ThemeVideo, + ["extras"] = ExtraType.Unknown, + ["behind the scenes"] = ExtraType.BehindTheScenes, + ["deleted scenes"] = ExtraType.DeletedScene, + ["interviews"] = ExtraType.Interview, + ["scenes"] = ExtraType.Scene, + ["samples"] = ExtraType.Sample, + ["shorts"] = ExtraType.Clip, + ["featurettes"] = ExtraType.Clip + }; + Compile(); } + /// <summary> + /// Gets or sets the folder name to extra types mapping. + /// </summary> + public Dictionary<string, ExtraType> AllExtrasTypesFolderNames { get; set; } + /// <summary> /// Gets or sets list of audio file extensions. /// </summary> diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index bce7cb47f1..309b18b532 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -21,13 +21,8 @@ namespace Emby.Naming.Video /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> /// <param name="parseName">Whether to parse the name or use the filename.</param> /// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns> - public static IReadOnlyList<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true) + public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true) { - var videoInfos = files - .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions, parseName)) - .OfType<VideoFileInfo>() - .ToList(); - // Filter out all extras, otherwise they could cause stacks to not be resolved // See the unit test TestStackedWithTrailer var nonExtras = videoInfos diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 29758a0788..e558fbe27b 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -54,20 +54,10 @@ namespace Emby.Server.Implementations.Library { if (parent != null) { - // Ignore trailer folders but allow it at the collection level - if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase) - && !(parent is AggregateFolder) - && !(parent is UserRootFolder)) - { - return true; - } - - if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) + // Ignore extras folders but allow it at the collection level + if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename) + && parent is not AggregateFolder + && parent is not UserRootFolder) { return true; } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 8cc9a2fd5e..224550f5fd 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -531,8 +531,8 @@ namespace Emby.Server.Implementations.Library return key.GetMD5(); } - public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null) - => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent); + public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, IDirectoryService directoryService = null) + => ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent); private BaseItem ResolvePath( FileSystemMetadata fileInfo, @@ -652,7 +652,7 @@ namespace Emby.Server.Implementations.Library return !args.ContainsFileSystemEntryByName(".ignore"); } - public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType) + public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType = null) { return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers); } @@ -2683,7 +2683,7 @@ namespace Emby.Server.Implementations.Library }; } - public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren) + public IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) { var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions); if (ownerVideoInfo == null) @@ -2692,17 +2692,36 @@ namespace Emby.Server.Implementations.Library } var count = fileSystemChildren.Count; - var files = new List<FileSystemMetadata>(); + var files = new List<VideoFileInfo>(); + var nonVideoFiles = new List<FileSystemMetadata>(); for (var i = 0; i < count; i++) { var current = fileSystemChildren[i]; - if (current.IsDirectory && BaseItem.AllExtrasTypesFolderNames.ContainsKey(current.Name)) + if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name)) { - files.AddRange(_fileSystem.GetFiles(current.FullName, _namingOptions.VideoFileExtensions, false, false)); + var filesInSubFolder = _fileSystem.GetFiles(current.FullName, _namingOptions.VideoFileExtensions, false, false); + foreach (var file in filesInSubFolder) + { + var videoInfo = VideoResolver.Resolve(file.FullName, file.IsDirectory, _namingOptions); + if (videoInfo == null) + { + nonVideoFiles.Add(file); + continue; + } + + files.Add(videoInfo); + } } else if (!current.IsDirectory) { - files.Add(current); + var videoInfo = VideoResolver.Resolve(current.FullName, current.IsDirectory, _namingOptions); + if (videoInfo == null) + { + nonVideoFiles.Add(current); + continue; + } + + files.Add(videoInfo); } } @@ -2714,11 +2733,10 @@ namespace Emby.Server.Implementations.Library var videos = VideoListResolver.Resolve(files, _namingOptions); // owner video info cannot be null as that implies it has no path var extras = ExtraResolver.GetExtras(videos, ownerVideoInfo, _namingOptions.VideoFlagDelimiters); - for (var i = 0; i < extras.Count; i++) { var currentExtra = extras[i]; - var resolved = ResolvePath(_fileSystem.GetFileInfo(currentExtra.Path)); + var resolved = ResolvePath(_fileSystem.GetFileInfo(currentExtra.Path), null, directoryService); if (resolved is not Video video) { continue; @@ -2735,6 +2753,35 @@ namespace Emby.Server.Implementations.Library video.OwnerId = owner.Id; yield return video; } + + // TODO: theme songs must be handled "manually" (but should we?) since they aren't video files + for (var i = 0; i < nonVideoFiles.Count; i++) + { + var current = nonVideoFiles[i]; + var extraInfo = ExtraResolver.GetExtraInfo(current.FullName, _namingOptions); + if (extraInfo.ExtraType != ExtraType.ThemeSong) + { + continue; + } + + var resolved = ResolvePath(current, null, directoryService); + if (resolved is not Audio themeSong) + { + continue; + } + + // Try to retrieve it from the db. If we don't find it, use the resolved version + if (GetItemById(themeSong.Id) is Audio dbItem) + { + themeSong = dbItem; + } + + themeSong.ExtraType = ExtraType.ThemeSong; + themeSong.OwnerId = owner.Id; + themeSong.ParentId = Guid.Empty; + + yield return themeSong; + } } public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem) diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 10f42b926c..4feaf3fb4c 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -261,7 +261,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } } - var resolverResult = VideoListResolver.Resolve(files, NamingOptions, supportMultiEditions, parseName); + var videoInfos = files + .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName)) + .Where(f => f != null) + .ToList(); + + var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName); var result = new MultiItemResolverResult { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 4114ff7b0b..cb21add125 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -13,7 +13,6 @@ using System.Threading.Tasks; using Diacritics.Extensions; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; -using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -22,7 +21,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; @@ -42,11 +40,7 @@ namespace MediaBrowser.Controller.Entities { private BaseItemKind? _baseItemKind; - public const string TrailerFileName = "trailer"; - public const string TrailersFolderName = "trailers"; - public const string ThemeSongsFolderName = "theme-music"; public const string ThemeSongFileName = "theme"; - public const string ThemeVideosFolderName = "backdrops"; /// <summary> /// The supported image extensions. @@ -83,21 +77,6 @@ namespace MediaBrowser.Controller.Entities Model.Entities.ExtraType.Scene }; - /// <summary> - /// The supported extra folder names and types. See <see cref="Emby.Naming.Common.NamingOptions" />. - /// </summary> - public static readonly Dictionary<string, ExtraType> AllExtrasTypesFolderNames = new Dictionary<string, ExtraType>(StringComparer.OrdinalIgnoreCase) - { - ["extras"] = MediaBrowser.Model.Entities.ExtraType.Unknown, - ["behind the scenes"] = MediaBrowser.Model.Entities.ExtraType.BehindTheScenes, - ["deleted scenes"] = MediaBrowser.Model.Entities.ExtraType.DeletedScene, - ["interviews"] = MediaBrowser.Model.Entities.ExtraType.Interview, - ["scenes"] = MediaBrowser.Model.Entities.ExtraType.Scene, - ["samples"] = MediaBrowser.Model.Entities.ExtraType.Sample, - ["shorts"] = MediaBrowser.Model.Entities.ExtraType.Clip, - ["featurettes"] = MediaBrowser.Model.Entities.ExtraType.Clip - }; - private string _sortName; private string _forcedSortName; @@ -1275,74 +1254,6 @@ namespace MediaBrowser.Controller.Entities return string.Join('/', terms); } - /// <summary> - /// Loads the theme songs. - /// </summary> - /// <returns>List{Audio.Audio}.</returns> - private static Audio.Audio[] LoadThemeSongs(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) - { - var files = fileSystemChildren.Where(i => i.IsDirectory) - .Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) - .SelectMany(i => FileSystem.GetFiles(i.FullName)) - .ToList(); - - // Support plex/xbmc convention - files.AddRange(fileSystemChildren - .Where(i => !i.IsDirectory && System.IO.Path.GetFileNameWithoutExtension(i.FullName.AsSpan()).Equals(ThemeSongFileName, StringComparison.OrdinalIgnoreCase))); - - return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions()) - .OfType<Audio.Audio>() - .Select(audio => - { - // Try to retrieve it from the db. If we don't find it, use the resolved version - if (LibraryManager.GetItemById(audio.Id) is Audio.Audio dbItem) - { - audio = dbItem; - } - else - { - // item is new - audio.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong; - } - - return audio; - - // Sort them so that the list can be easily compared for changes - }).OrderBy(i => i.Path).ToArray(); - } - - /// <summary> - /// Loads the video backdrops. - /// </summary> - /// <returns>List{Video}.</returns> - private static Video[] LoadThemeVideos(IEnumerable<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) - { - var files = fileSystemChildren.Where(i => i.IsDirectory) - .Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) - .SelectMany(i => FileSystem.GetFiles(i.FullName)); - - return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions()) - .OfType<Video>() - .Select(item => - { - // Try to retrieve it from the db. If we don't find it, use the resolved version - - if (LibraryManager.GetItemById(item.Id) is Video dbItem) - { - item = dbItem; - } - else - { - // item is new - item.ExtraType = Model.Entities.ExtraType.ThemeVideo; - } - - return item; - - // Sort them so that the list can be easily compared for changes - }).OrderBy(i => i.Path).ToArray(); - } - public Task RefreshMetadata(CancellationToken cancellationToken) { return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken); @@ -1453,7 +1364,7 @@ namespace MediaBrowser.Controller.Entities /// <returns><c>true</c> if any items have changed, else <c>false</c>.</returns> protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { - if (!IsFileProtocol || !SupportsOwnedItems || IsInMixedFolder || this is ICollectionFolder || this.GetType() == typeof(Folder)) + if (!IsFileProtocol || !SupportsOwnedItems || IsInMixedFolder || this is ICollectionFolder or UserRootFolder or AggregateFolder || this.GetType() == typeof(Folder)) { return false; } @@ -1470,7 +1381,7 @@ namespace MediaBrowser.Controller.Entities private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { - var extras = LibraryManager.FindExtras(item, fileSystemChildren).ToArray(); + var extras = LibraryManager.FindExtras(item, fileSystemChildren, options.DirectoryService).ToArray(); var newExtraIds = extras.Select(i => i.Id).ToArray(); var extrasChanged = !item.ExtraIds.SequenceEqual(newExtraIds); diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 1ae28abde5..eba92695eb 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -58,10 +58,12 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="fileInfo">The file information.</param> /// <param name="parent">The parent.</param> + /// <param name="directoryService">An instance of <see cref="IDirectoryService"/>.</param> /// <returns>BaseItem.</returns> BaseItem ResolvePath( FileSystemMetadata fileInfo, - Folder parent = null); + Folder parent = null, + IDirectoryService directoryService = null); /// <summary> /// Resolves a set of files into a list of BaseItem. @@ -430,10 +432,9 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="owner">The owner.</param> /// <param name="fileSystemChildren">The file system children.</param> - /// <returns>IEnumerable<Video>.</returns> - IEnumerable<Video> FindExtras( - BaseItem owner, - List<FileSystemMetadata> fileSystemChildren); + /// <param name="directoryService">An instance of <see cref="IDirectoryService"/>.</param> + /// <returns>IEnumerable<BaseItem>.</returns> + IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService); /// <summary> /// Gets the collection folders. diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index efd02aef06..de505b686a 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; @@ -120,7 +121,7 @@ namespace MediaBrowser.LocalMetadata.Images return directoryService.GetFileSystemEntries(path) .Where(i => (includeDirectories && i.IsDirectory) - || Array.FindIndex(BaseItem.SupportedImageExtensions, ext => string.Equals(ext, i.Extension, StringComparison.OrdinalIgnoreCase)) != -1) + || BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparison.OrdinalIgnoreCase)) .OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty)); } diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index 323457d094..9a9a57be47 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -23,11 +23,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result.Where(v => v.ExtraType == null)); @@ -46,11 +42,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result.Where(v => v.ExtraType == null)); @@ -68,11 +60,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -94,11 +82,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(7, result.Count); @@ -121,11 +105,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -149,11 +129,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(9, result.Count); @@ -173,11 +149,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(5, result.Count); @@ -199,11 +171,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(5, result.Count); @@ -226,11 +194,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -256,11 +220,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -280,11 +240,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -305,11 +261,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(7, result.Count); @@ -331,11 +283,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(5, result.Count); @@ -352,11 +300,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -373,11 +317,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -394,11 +334,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -415,11 +351,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -428,7 +360,7 @@ namespace Jellyfin.Naming.Tests.Video [Fact] public void TestEmptyList() { - var result = VideoListResolver.Resolve(new List<FileSystemMetadata>(), _namingOptions).ToList(); + var result = VideoListResolver.Resolve(new List<VideoFileInfo>(), _namingOptions).ToList(); Assert.Empty(result); } diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index cda4967613..b761878426 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -42,11 +42,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(11, result.Count); @@ -80,11 +76,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -100,11 +92,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -122,11 +110,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -145,11 +129,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(3, result.Count); @@ -169,11 +149,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(3, result.Count); @@ -192,11 +168,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -218,11 +190,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(5, result.Count); @@ -238,11 +206,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = true, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, true, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -259,11 +223,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = true, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, true, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -281,11 +241,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(3, result.Count); @@ -306,11 +262,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(4, result.Count); @@ -332,11 +284,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -351,11 +299,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -370,11 +314,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -390,11 +330,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Single(result); @@ -410,11 +346,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -430,11 +362,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -452,11 +380,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); @@ -474,11 +398,7 @@ namespace Jellyfin.Naming.Tests.Video }; var result = VideoListResolver.Resolve( - files.Select(i => new FileSystemMetadata - { - IsDirectory = false, - FullName = i - }).ToList(), + files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(), _namingOptions).ToList(); Assert.Equal(2, result.Count); diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs index 10afadbeff..b29426d855 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs @@ -5,11 +5,13 @@ using AutoFixture; using AutoFixture.AutoMoq; using Emby.Naming.Common; using Emby.Server.Implementations.Library.Resolvers; +using Emby.Server.Implementations.Library.Resolvers.Audio; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Entities; @@ -22,6 +24,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library.LibraryManager; public class FindExtrasTests { private readonly Emby.Server.Implementations.Library.LibraryManager _libraryManager; + private readonly Mock<IFileSystem> _fileSystemMock; public FindExtrasTests() { @@ -29,11 +32,11 @@ public class FindExtrasTests fixture.Register(() => new NamingOptions()); var configMock = fixture.Freeze<Mock<IServerConfigurationManager>>(); configMock.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data"); - var fileSystemMock = fixture.Freeze<Mock<IFileSystem>>(); - fileSystemMock.Setup(f => f.GetFileInfo(It.IsAny<string>())).Returns<string>(path => new FileSystemMetadata { FullName = path }); + _fileSystemMock = fixture.Freeze<Mock<IFileSystem>>(); + _fileSystemMock.Setup(f => f.GetFileInfo(It.IsAny<string>())).Returns<string>(path => new FileSystemMetadata { FullName = path }); _libraryManager = fixture.Build<Emby.Server.Implementations.Library.LibraryManager>().Do(s => s.AddParts( fixture.Create<IEnumerable<IResolverIgnoreRule>>(), - new List<IItemResolver> { new GenericVideoResolver<Video>(fixture.Create<NamingOptions>()) }, + new List<IItemResolver> { new GenericVideoResolver<Video>(fixture.Create<NamingOptions>()), new AudioResolver(fixture.Create<NamingOptions>()) }, fixture.Create<IEnumerable<IIntroProvider>>(), fixture.Create<IEnumerable<IBaseItemComparer>>(), fixture.Create<IEnumerable<ILibraryPostScanTask>>())) @@ -62,7 +65,7 @@ public class FindExtrasTests IsDirectory = false }).ToList(); - var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList(); Assert.Equal(2, extras.Count); Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); @@ -77,26 +80,77 @@ public class FindExtrasTests { "/movies/Up/Up.mkv", "/movies/Up/Up - trailer.mkv", - "/movies/Up/trailers/some trailer.mkv", - "/movies/Up/behind the scenes/the making of Up.mkv", + "/movies/Up/trailers", + "/movies/Up/theme-music", + "/movies/Up/theme.mp3", + "/movies/Up/not a theme.mp3", + "/movies/Up/behind the scenes", "/movies/Up/behind the scenes.mkv", "/movies/Up/Up - sample.mkv", "/movies/Up/Up something else.mkv" }; + _fileSystemMock.Setup(f => f.GetFiles( + "/movies/Up/trailers", + It.IsAny<string[]>(), + false, + false)) + .Returns(new List<FileSystemMetadata> + { + new () + { + FullName = "/movies/Up/trailers/some trailer.mkv", + Name = "some trailer.mkv", + IsDirectory = false + } + }); + + _fileSystemMock.Setup(f => f.GetFiles( + "/movies/Up/behind the scenes", + It.IsAny<string[]>(), + false, + false)) + .Returns(new List<FileSystemMetadata> + { + new () + { + FullName = "/movies/Up/behind the scenes/the making of Up.mkv", + Name = "the making of Up.mkv", + IsDirectory = false + } + }); + + _fileSystemMock.Setup(f => f.GetFiles( + "/movies/Up/theme-music", + It.IsAny<string[]>(), + false, + false)) + .Returns(new List<FileSystemMetadata> + { + new () + { + FullName = "/movies/Up/theme-music/theme2.mp3", + Name = "theme2.mp3", + IsDirectory = false + } + }); + var files = paths.Select(p => new FileSystemMetadata { FullName = p, - IsDirectory = false + Name = Path.GetFileName(p), + IsDirectory = string.IsNullOrEmpty(Path.GetExtension(p)) }).ToList(); - var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList(); - Assert.Equal(4, extras.Count); + Assert.Equal(6, extras.Count); Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); Assert.Equal(ExtraType.Trailer, extras[1].ExtraType); Assert.Equal(ExtraType.BehindTheScenes, extras[2].ExtraType); Assert.Equal(ExtraType.Sample, extras[3].ExtraType); + Assert.Equal(ExtraType.ThemeSong, extras[4].ExtraType); + Assert.Equal(ExtraType.ThemeSong, extras[5].ExtraType); } [Fact] @@ -116,7 +170,7 @@ public class FindExtrasTests IsDirectory = false }).ToList(); - var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList(); Assert.Single(extras); Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); @@ -142,7 +196,7 @@ public class FindExtrasTests IsDirectory = false }).ToList(); - var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList(); Assert.Single(extras); Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); @@ -167,7 +221,7 @@ public class FindExtrasTests IsDirectory = string.IsNullOrEmpty(Path.GetExtension(p)) }).ToList(); - var extras = _libraryManager.FindExtras(owner, files).OrderBy(e => e.ExtraType).ToList(); + var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList(); Assert.Equal(2, extras.Count); Assert.Equal(ExtraType.Trailer, extras[0].ExtraType); From 91292b8ea533b01825cedca7b039c9943f55f2eb Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Mon, 20 Dec 2021 12:34:16 +0100 Subject: [PATCH 156/167] Fix build --- Emby.Naming/Video/VideoListResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 309b18b532..4fc849256b 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -16,7 +16,7 @@ namespace Emby.Naming.Video /// <summary> /// Resolves alternative versions and extras from list of video files. /// </summary> - /// <param name="files">List of related video files.</param> + /// <param name="videoInfos">List of related video files.</param> /// <param name="namingOptions">The naming options.</param> /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> /// <param name="parseName">Whether to parse the name or use the filename.</param> From b880dc8a4a2d1820c6063f441edce42ab399d79e Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Mon, 20 Dec 2021 13:31:07 +0100 Subject: [PATCH 157/167] Use our own Contains extension --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 8 -------- Emby.Dlna/DlnaManager.cs | 1 - Emby.Naming/AudioBook/AudioBookResolver.cs | 4 ++-- Emby.Naming/Subtitles/SubtitleParser.cs | 11 ++++++----- Emby.Naming/TV/EpisodeResolver.cs | 4 ++-- Emby.Naming/TV/SeriesPathParser.cs | 1 - Emby.Naming/Video/CleanStringParser.cs | 1 - Emby.Naming/Video/FileStack.cs | 4 ++-- Emby.Naming/Video/StubResolver.cs | 4 ++-- Emby.Notifications/NotificationEntryPoint.cs | 3 ++- Emby.Photos/PhotoProvider.cs | 3 ++- .../AppBase/ConfigurationHelper.cs | 1 - .../Channels/ChannelManager.cs | 7 ++++--- .../Data/BaseSqliteRepository.cs | 4 ++-- Emby.Server.Implementations/Dto/DtoService.cs | 4 ++-- .../EntryPoints/ExternalPortForwarding.cs | 1 - .../HttpServer/Security/AuthService.cs | 1 - .../Images/DynamicImageProvider.cs | 3 ++- .../Images/GenreImageProvider.cs | 2 -- .../Library/MediaStreamSelector.cs | 15 ++++++++------- .../Resolvers/Audio/MusicArtistResolver.cs | 2 -- .../Library/Resolvers/Books/BookResolver.cs | 3 ++- .../Library/Resolvers/PhotoResolver.cs | 3 ++- .../Library/Resolvers/PlaylistResolver.cs | 5 +++-- .../Library/Resolvers/TV/EpisodeResolver.cs | 1 - .../Library/Resolvers/TV/SeriesResolver.cs | 3 --- .../Library/SearchEngine.cs | 3 --- .../Library/UserViewManager.cs | 8 +++----- .../LiveTv/EmbyTV/EmbyTV.cs | 9 +++++---- .../LiveTv/Listings/SchedulesDirect.cs | 19 +++++++++---------- .../LiveTv/Listings/XmlTvListingsProvider.cs | 8 ++++---- .../LiveTv/LiveTvDtoService.cs | 1 - .../LiveTv/LiveTvManager.cs | 2 -- .../HdHomerun/HdHomerunUdpStream.cs | 1 - .../LiveTv/TunerHosts/M3UTunerHost.cs | 3 ++- .../MediaEncoder/EncodingManager.cs | 5 +++-- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 3 ++- .../Updates/InstallationManager.cs | 1 - Jellyfin.Api/Controllers/FilterController.cs | 1 - Jellyfin.Api/Controllers/ItemsController.cs | 1 - Jellyfin.Api/Controllers/LibraryController.cs | 13 ++++++------- Jellyfin.Api/Controllers/MoviesController.cs | 2 -- Jellyfin.Api/Controllers/PluginsController.cs | 1 - .../Controllers/RemoteImageController.cs | 5 ----- Jellyfin.Api/Controllers/SearchController.cs | 1 - Jellyfin.Api/Controllers/SystemController.cs | 1 - .../Controllers/UserLibraryController.cs | 1 - Jellyfin.Api/Controllers/YearsController.cs | 3 ++- .../Devices/DeviceManager.cs | 3 ++- .../Entities/Audio/Audio.cs | 2 -- MediaBrowser.Controller/Entities/BaseItem.cs | 16 ++++++++-------- .../Entities/TagExtensions.cs | 3 ++- MediaBrowser.Controller/Entities/UserView.cs | 5 +++-- .../Entities/UserViewBuilder.cs | 18 ++++++++---------- MediaBrowser.Controller/Entities/Video.cs | 3 ++- .../IServerApplicationHost.cs | 1 - .../LiveTv/LiveTvChannel.cs | 4 ++-- .../LiveTv/LiveTvProgram.cs | 11 ++++++----- MediaBrowser.Controller/LiveTv/TimerInfo.cs | 12 ++++++------ .../Providers/MetadataRefreshOptions.cs | 3 ++- .../InternalMetadataFolderImageProvider.cs | 1 - .../Probing/ProbeResultNormalizer.cs | 4 ++-- MediaBrowser.Model/Dlna/CodecProfile.cs | 4 ++-- MediaBrowser.Model/Dlna/ConditionProcessor.cs | 4 ++-- MediaBrowser.Model/Dlna/ContainerProfile.cs | 4 ++-- MediaBrowser.Model/Dlna/DeviceProfile.cs | 12 ++++++------ MediaBrowser.Model/Dlna/SubtitleProfile.cs | 4 ++-- MediaBrowser.Model/Entities/ImageType.cs | 2 -- .../Notifications/NotificationOptions.cs | 8 ++++---- MediaBrowser.Providers/Manager/ImageSaver.cs | 5 +++-- .../Manager/ProviderManager.cs | 7 ++++--- .../Playlists/PlaylistItemsProvider.cs | 3 ++- .../Plugins/Omdb/OmdbImageProvider.cs | 1 - .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 5 +++-- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 5 +++-- .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 5 +++-- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 5 +++-- .../Subtitles/SubtitleManager.cs | 4 ++-- .../Savers/BaseNfoSaver.cs | 3 ++- RSSDP/HttpRequestParser.cs | 4 ++-- RSSDP/HttpResponseParser.cs | 4 ++-- .../BaseItemManagerTests.cs | 1 - .../Updates/InstallationManagerTests.cs | 2 -- .../AuthHelper.cs | 1 - .../Controllers/DlnaControllerTests.cs | 2 -- .../MediaStructureControllerTests.cs | 2 -- .../Controllers/StartupControllerTests.cs | 1 - .../Controllers/UserControllerTests.cs | 2 -- 88 files changed, 169 insertions(+), 208 deletions(-) diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index b354421eaa..fde3f2f893 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -18,11 +18,8 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; @@ -30,12 +27,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; -using Book = MediaBrowser.Controller.Entities.Book; -using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Genre = MediaBrowser.Controller.Entities.Genre; -using Movie = MediaBrowser.Controller.Entities.Movies.Movie; -using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; -using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Dlna.ContentDirectory { diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 93efa4b38d..d9d2a345ad 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.IO; using System.Linq; using System.Reflection; -using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs index f6ad3601d7..183b6c3b11 100644 --- a/Emby.Naming/AudioBook/AudioBookResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -1,7 +1,7 @@ using System; using System.IO; -using System.Linq; using Emby.Naming.Common; +using Jellyfin.Extensions; namespace Emby.Naming.AudioBook { @@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook var extension = Path.GetExtension(path); // Check supported extensions - if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!_options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { return null; } diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs index a19340ef69..5809c512a8 100644 --- a/Emby.Naming/Subtitles/SubtitleParser.cs +++ b/Emby.Naming/Subtitles/SubtitleParser.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Linq; using Emby.Naming.Common; +using Jellyfin.Extensions; namespace Emby.Naming.Subtitles { @@ -34,7 +35,7 @@ namespace Emby.Naming.Subtitles } var extension = Path.GetExtension(path); - if (!_options.SubtitleFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!_options.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { return null; } @@ -42,11 +43,11 @@ namespace Emby.Naming.Subtitles var flags = GetFlags(path); var info = new SubtitleInfo( path, - _options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)), - _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase))); + _options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)), + _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase))); - var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) - && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase)) + var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparison.OrdinalIgnoreCase) + && !_options.SubtitleForcedFlags.Contains(i, StringComparison.OrdinalIgnoreCase)) .ToList(); // Should have a name, language and file extension diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index 5e952e47b7..6cebc40c27 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -1,8 +1,8 @@ using System; using System.IO; -using System.Linq; using Emby.Naming.Common; using Emby.Naming.Video; +using Jellyfin.Extensions; namespace Emby.Naming.TV { @@ -48,7 +48,7 @@ namespace Emby.Naming.TV { var extension = Path.GetExtension(path); // Check supported extensions - if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { // It's not supported. Check stub extensions if (!StubResolver.TryResolveFile(path, _options, out stubType)) diff --git a/Emby.Naming/TV/SeriesPathParser.cs b/Emby.Naming/TV/SeriesPathParser.cs index a62e5f4d63..4dfbb36a37 100644 --- a/Emby.Naming/TV/SeriesPathParser.cs +++ b/Emby.Naming/TV/SeriesPathParser.cs @@ -1,4 +1,3 @@ -using System.Globalization; using Emby.Naming.Common; namespace Emby.Naming.TV diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index b813335008..a336f8fbd1 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs index bd635a9f78..4902e6728e 100644 --- a/Emby.Naming/Video/FileStack.cs +++ b/Emby.Naming/Video/FileStack.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; +using Jellyfin.Extensions; namespace Emby.Naming.Video { @@ -50,7 +50,7 @@ namespace Emby.Naming.Video return false; } - return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparer.OrdinalIgnoreCase); + return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparison.OrdinalIgnoreCase); } } } diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs index 079987fe8a..f7ba606e3e 100644 --- a/Emby.Naming/Video/StubResolver.cs +++ b/Emby.Naming/Video/StubResolver.cs @@ -1,7 +1,7 @@ using System; using System.IO; -using System.Linq; using Emby.Naming.Common; +using Jellyfin.Extensions; namespace Emby.Naming.Video { @@ -28,7 +28,7 @@ namespace Emby.Naming.Video var extension = Path.GetExtension(path); - if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { return false; } diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs index e8ae14ff22..a56df70312 100644 --- a/Emby.Notifications/NotificationEntryPoint.cs +++ b/Emby.Notifications/NotificationEntryPoint.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; @@ -104,7 +105,7 @@ namespace Emby.Notifications var type = entry.Type; - if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase)) { return; } diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index 4071e4e547..cef82b4d66 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -60,7 +61,7 @@ namespace Emby.Photos item.SetImagePath(ImageType.Primary, item.Path); // Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs - if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparer.OrdinalIgnoreCase)) + if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparison.OrdinalIgnoreCase)) { try { diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 3a916e5c01..f923e59efb 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using MediaBrowser.Model.Serialization; namespace Emby.Server.Implementations.AppBase diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 8c167824ee..8702691d1d 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -179,7 +180,7 @@ namespace Emby.Server.Implementations.Channels try { return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes - && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val; + && hasAttributes.Attributes.Contains("Recordings", StringComparison.OrdinalIgnoreCase)) == val; } catch { @@ -1135,7 +1136,7 @@ namespace Emby.Server.Implementations.Channels if (!info.IsLiveStream) { - if (item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase)) + if (item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase)) { item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray(); _logger.LogDebug("Forcing update due to Tags {0}", item.Name); @@ -1144,7 +1145,7 @@ namespace Emby.Server.Implementations.Channels } else { - if (!item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase)) + if (!item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase)) { item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray(); _logger.LogDebug("Forcing update due to Tags {0}", item.Name); diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 73c31f49d1..5030cbacb8 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; +using Jellyfin.Extensions; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; @@ -194,7 +194,7 @@ namespace Emby.Server.Implementations.Data protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames) { - if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase)) + if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase)) { return; } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index a34bfdb750..365aa83684 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -7,9 +7,9 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; @@ -294,7 +294,7 @@ namespace Emby.Server.Implementations.Dto path = path.TrimStart('.'); } - if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase)) { fileExtensionContainer = path; } diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index d325fa14fc..06e57ad127 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -13,7 +13,6 @@ using Jellyfin.Networking.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Plugins; -using MediaBrowser.Model.Dlna; using Microsoft.Extensions.Logging; using Mono.Nat; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index e7103ec958..1d04f3da37 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Jellyfin.Data.Enums; -using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 0c3fe33a38..5756806535 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -35,7 +36,7 @@ namespace Emby.Server.Implementations.Images var view = (UserView)item; var isUsingCollectionStrip = IsUsingCollectionStrip(view); - var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase); var result = view.GetItemList(new InternalItemsQuery { diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index f8eefad6b5..968bf5fa33 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -8,8 +8,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index b3837fedb4..da0c89c13e 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Model.Entities; namespace Emby.Server.Implementations.Library @@ -64,18 +65,18 @@ namespace Emby.Server.Implementations.Library stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault); // if the audio language is not understood by the user, load their preferred subs, if there are any - if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) + if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) { - stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); + stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)); } } else if (mode == SubtitlePlaybackMode.Smart) { // if the audio language is not understood by the user, load their preferred subs, if there are any - if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) + if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) { - stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); + stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)); } } else if (mode == SubtitlePlaybackMode.Always) @@ -136,9 +137,9 @@ namespace Emby.Server.Implementations.Library else if (mode == SubtitlePlaybackMode.Smart) { // Prefer smart logic over embedded metadata - if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) + if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) { - filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) + filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) .ToList(); } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 27e18be42c..210ed0953a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -4,12 +4,10 @@ using System; using System.Linq; using System.Threading.Tasks; using Emby.Naming.Common; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library.Resolvers.Audio diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index e685c87f1a..8f224f547a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Linq; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; @@ -32,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books var extension = Path.GetExtension(args.Path); - if (extension != null && _validExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (extension != null && _validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { // It's a book return new Book diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 51d8193037..e52b430500 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using Emby.Naming.Common; using Emby.Naming.Video; +using Jellyfin.Extensions; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -109,7 +110,7 @@ namespace Emby.Server.Implementations.Library.Resolvers } string extension = Path.GetExtension(path).TrimStart('.'); - return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase); + return imageProcessor.SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase); } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index 8ce59717d0..6b0dfe9864 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Linq; +using Jellyfin.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Resolvers; @@ -57,10 +58,10 @@ namespace Emby.Server.Implementations.Library.Resolvers // Check if this is a music playlist file // It should have the correct collection type and a supported file extension - else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { var extension = Path.GetExtension(args.Path); - if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { return new Playlist { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 928cd42ddd..be99056470 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -3,7 +3,6 @@ using System; using System.Linq; using Emby.Naming.Common; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 46e36847dc..f5ac3c665e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -5,12 +5,9 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Emby.Naming.Common; using Emby.Naming.TV; using Emby.Naming.Video; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 42374b2a2b..4aacf7774c 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -10,12 +10,9 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; -using Genre = MediaBrowser.Controller.Entities.Genre; -using Person = MediaBrowser.Controller.Entities.Person; namespace Emby.Server.Implementations.Library { diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 3593986a9c..ab8bc6328d 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -8,11 +8,11 @@ using System.Linq; using System.Threading; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Channels; @@ -20,8 +20,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; -using Genre = MediaBrowser.Controller.Entities.Genre; -using Person = MediaBrowser.Controller.Entities.Person; namespace Emby.Server.Implementations.Library { @@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Library continue; } - if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { list.Add(GetUserView(folder, folderViewType, string.Empty)); } @@ -180,7 +178,7 @@ namespace Emby.Server.Implementations.Library { if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase))) { - if (!presetViews.Contains(viewType, StringComparer.OrdinalIgnoreCase)) + if (!presetViews.Contains(viewType, StringComparison.OrdinalIgnoreCase)) { return (Folder)parents[0]; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index e604000ad2..7ef93d1669 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -17,6 +17,7 @@ using System.Xml; using Emby.Server.Implementations.Library; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV foreach (var virtualFolder in virtualFolders) { - if (!virtualFolder.Locations.Contains(path, StringComparer.OrdinalIgnoreCase)) + if (!virtualFolder.Locations.Contains(path, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -891,7 +892,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new ArgumentNullException(nameof(tunerHostId)); } - return info.EnabledTuners.Contains(tunerHostId, StringComparer.OrdinalIgnoreCase); + return info.EnabledTuners.Contains(tunerHostId, StringComparison.OrdinalIgnoreCase); } public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) @@ -2332,7 +2333,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var deletes = _timerProvider.GetAll() .Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase)) - .Where(i => !allTimerIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow) + .Where(i => !allTimerIds.Contains(i.Id, StringComparison.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow) .Where(i => deleteStatuses.Contains(i.Status)) .ToList(); @@ -2621,7 +2622,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (newDevicesOnly) { - discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase)) + discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase)) .ToList(); } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 93d72dba44..dd0cb6c5de 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Json; -using System.Net.Http.Headers; using System.Net.Mime; using System.Security.Cryptography; using System.Text; @@ -242,19 +241,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings if (programInfo.AudioProperties.Count != 0) { - if (programInfo.AudioProperties.Contains("atmos", StringComparer.OrdinalIgnoreCase)) + if (programInfo.AudioProperties.Contains("atmos", StringComparison.OrdinalIgnoreCase)) { audioType = ProgramAudio.Atmos; } - else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparer.OrdinalIgnoreCase)) + else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparison.OrdinalIgnoreCase)) { audioType = ProgramAudio.DolbyDigital; } - else if (programInfo.AudioProperties.Contains("dd", StringComparer.OrdinalIgnoreCase)) + else if (programInfo.AudioProperties.Contains("dd", StringComparison.OrdinalIgnoreCase)) { audioType = ProgramAudio.DolbyDigital; } - else if (programInfo.AudioProperties.Contains("stereo", StringComparer.OrdinalIgnoreCase)) + else if (programInfo.AudioProperties.Contains("stereo", StringComparison.OrdinalIgnoreCase)) { audioType = ProgramAudio.Stereo; } @@ -316,8 +315,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings if (programInfo.VideoProperties != null) { - info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase); - info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase); + info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparison.OrdinalIgnoreCase); + info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparison.OrdinalIgnoreCase); } if (details.ContentRating != null && details.ContentRating.Count > 0) @@ -326,7 +325,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings .Replace("--", "-", StringComparison.Ordinal); var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" }; - if (invalid.Contains(info.OfficialRating, StringComparer.OrdinalIgnoreCase)) + if (invalid.Contains(info.OfficialRating, StringComparison.OrdinalIgnoreCase)) { info.OfficialRating = null; } @@ -388,9 +387,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings if (details.Genres != null) { info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList(); - info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase); + info.IsNews = details.Genres.Contains("news", StringComparison.OrdinalIgnoreCase); - if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase)) + if (info.Genres.Contains("children", StringComparison.OrdinalIgnoreCase)) { info.IsKids = true; } diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 0c0ec48d96..3da9d02b83 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -190,10 +190,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings IsSeries = program.Episode != null, IsRepeat = program.IsPreviouslyShown && !program.IsNew, IsPremiere = program.Premiere != null, - IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)), - IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparer.OrdinalIgnoreCase)), - IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)), - IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)), + IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), + IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), + IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), + IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), ImageUrl = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source) ? program.Icon.Source : null, HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source), OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null, diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 317bcbfdd2..323b960210 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -13,7 +13,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dto; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 2f826c63e4..047d8e98c9 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -33,8 +33,6 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; -using Episode = MediaBrowser.Controller.Entities.TV.Episode; -using Movie = MediaBrowser.Controller.Entities.Movies.Movie; namespace Emby.Server.Implementations.LiveTv { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 9fba17ca3b..1f02acfa3d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -3,7 +3,6 @@ #pragma warning disable CS1591 using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 08b9260b98..99486f25ca 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -119,7 +120,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { var extension = Path.GetExtension(mediaSource.Path) ?? string.Empty; - if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); } diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index ac6606d39a..6e1dc725d4 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -120,7 +121,7 @@ namespace Emby.Server.Implementations.MediaEncoder var path = GetChapterImagePath(video, chapter.StartPositionTicks); - if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase)) + if (!currentImages.Contains(path, StringComparison.OrdinalIgnoreCase)) { if (extractImages) { @@ -219,7 +220,7 @@ namespace Emby.Server.Implementations.MediaEncoder { var deadImages = images .Except(chapters.Select(i => i.ImagePath).Where(i => !string.IsNullOrEmpty(i)), StringComparer.OrdinalIgnoreCase) - .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparer.OrdinalIgnoreCase)) + .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparison.OrdinalIgnoreCase)) .ToList(); foreach (var image in deadImages) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 09ea6271d9..8b185419f3 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -143,7 +144,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var key = video.Path + video.DateModified.Ticks; - var extract = !previouslyFailedImages.Contains(key, StringComparer.OrdinalIgnoreCase); + var extract = !previouslyFailedImages.Contains(key, StringComparison.OrdinalIgnoreCase); try { diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 7f7eec7d93..24d592525d 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -20,7 +20,6 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Updates; using Microsoft.Extensions.Logging; diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index 02a0785e76..e170436d16 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using Jellyfin.Api.Constants; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 22e3fd202e..65c0662d21 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -8,7 +8,6 @@ using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index d4cc3810a0..c8f3bbc86d 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -15,6 +15,7 @@ using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LibraryDtos; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -23,7 +24,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Activity; @@ -37,7 +37,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Book = MediaBrowser.Controller.Entities.Book; namespace Jellyfin.Api.Controllers { @@ -786,7 +785,7 @@ namespace Jellyfin.Api.Controllers var typesList = types.ToList(); var plugins = _providerManager.GetAllMetadataPlugins() - .Where(i => types.Contains(i.ItemType, StringComparer.OrdinalIgnoreCase)) + .Where(i => types.Contains(i.ItemType, StringComparison.OrdinalIgnoreCase)) .OrderBy(i => typesList.IndexOf(i.ItemType)) .ToList(); @@ -941,10 +940,10 @@ namespace Jellyfin.Api.Controllers } var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions - .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) .ToArray(); - return metadataOptions.Length == 0 || metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparer.OrdinalIgnoreCase)); + return metadataOptions.Length == 0 || metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparison.OrdinalIgnoreCase)); } private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary) @@ -968,7 +967,7 @@ namespace Jellyfin.Api.Controllers .ToArray(); return metadataOptions.Length == 0 - || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); + || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase)); } private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary) @@ -998,7 +997,7 @@ namespace Jellyfin.Api.Controllers return true; } - return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); + return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase)); } } } diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index def10f0bd6..db72ff2f83 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -11,9 +11,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 0778ea3fc5..b41df1abb1 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -9,7 +9,6 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Models.PluginDtos; using Jellyfin.Extensions.Json; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Model.Net; diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index 773cff1ac6..9f57a5cdb6 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -3,18 +3,13 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Constants; -using Jellyfin.Extensions; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 02ee7860f7..26acb4cdcc 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Drawing; diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 2ff85fd2ae..411c987f39 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; -using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Jellyfin.Api.Attributes; diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index ae7e13f671..f6fbdc3025 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using Jellyfin.Extensions; diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 2bba2b97d7..8be6fd1b52 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -209,7 +210,7 @@ namespace Jellyfin.Api.Controllers } // Include MediaTypes - if (mediaTypes.Count > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (mediaTypes.Count > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { return false; } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index 0655c9813d..a55949df83 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -7,6 +7,7 @@ using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.Data.Queries; +using Jellyfin.Extensions; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Devices; @@ -219,7 +220,7 @@ namespace Jellyfin.Server.Implementations.Devices return true; } - return user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase) + return user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparison.OrdinalIgnoreCase) || !GetCapabilities(deviceId).SupportsPersistentIdentifier; } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index e90a2f56a8..9d0187c8c2 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -8,10 +8,8 @@ using System.Globalization; using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Entities.Audio { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 82d11c523e..357a9b77f8 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1372,11 +1372,11 @@ namespace MediaBrowser.Controller.Entities { try { - var files = IsFileProtocol ? - GetFileSystemChildren(options.DirectoryService).ToList() : - new List<FileSystemMetadata>(); + if (IsFileProtocol) + { + requiresSave = await RefreshedOwnedItems(options, GetFileSystemChildren(options.DirectoryService).ToList(), cancellationToken).ConfigureAwait(false); + } - requiresSave = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh } catch (Exception ex) @@ -1731,7 +1731,7 @@ namespace MediaBrowser.Controller.Entities private bool IsVisibleViaTags(User user) { - if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase))) + if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => Tags.Contains(i, StringComparison.OrdinalIgnoreCase))) { return false; } @@ -1900,7 +1900,7 @@ namespace MediaBrowser.Controller.Entities var current = Studios; - if (!current.Contains(name, StringComparer.OrdinalIgnoreCase)) + if (!current.Contains(name, StringComparison.OrdinalIgnoreCase)) { int curLen = current.Length; if (curLen == 0) @@ -1935,7 +1935,7 @@ namespace MediaBrowser.Controller.Entities } var genres = Genres; - if (!genres.Contains(name, StringComparer.OrdinalIgnoreCase)) + if (!genres.Contains(name, StringComparison.OrdinalIgnoreCase)) { var list = genres.ToList(); list.Add(name); @@ -2141,7 +2141,7 @@ namespace MediaBrowser.Controller.Entities .ToList(); var deletedImages = ImageInfos - .Where(image => image.IsLocalFile && !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase)) + .Where(image => image.IsLocalFile && !allFiles.Contains(image.Path, StringComparison.OrdinalIgnoreCase)) .ToList(); if (deletedImages.Count > 0) diff --git a/MediaBrowser.Controller/Entities/TagExtensions.cs b/MediaBrowser.Controller/Entities/TagExtensions.cs index 2ce396daf0..ec3eb0f706 100644 --- a/MediaBrowser.Controller/Entities/TagExtensions.cs +++ b/MediaBrowser.Controller/Entities/TagExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using Jellyfin.Extensions; namespace MediaBrowser.Controller.Entities { @@ -16,7 +17,7 @@ namespace MediaBrowser.Controller.Entities var current = item.Tags; - if (!current.Contains(name, StringComparer.OrdinalIgnoreCase)) + if (!current.Contains(name, StringComparison.OrdinalIgnoreCase)) { if (current.Length == 0) { diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index a6f1078494..5c9be73377 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Extensions; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; @@ -170,12 +171,12 @@ namespace MediaBrowser.Controller.Entities public static bool IsEligibleForGrouping(string viewType) { - return _viewTypesEligibleForGrouping.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + return _viewTypesEligibleForGrouping.Contains(viewType ?? string.Empty, StringComparison.OrdinalIgnoreCase); } public static bool EnableOriginalFolder(string viewType) { - return _originalFolderViewTypes.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + return _originalFolderViewTypes.Contains(viewType ?? string.Empty, StringComparison.OrdinalIgnoreCase); } protected override Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMetadata, Providers.MetadataRefreshOptions refreshOptions, Providers.IDirectoryService directoryService, System.Threading.CancellationToken cancellationToken) diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index aacf1498fd..fe44f1169e 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -8,7 +8,7 @@ using System.Globalization; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; -using MediaBrowser.Controller.Entities.Movies; +using Jellyfin.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; @@ -16,8 +16,6 @@ using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; -using Movie = MediaBrowser.Controller.Entities.Movies.Movie; -using Season = MediaBrowser.Controller.Entities.TV.Season; using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Controller.Entities @@ -494,7 +492,7 @@ namespace MediaBrowser.Controller.Entities public static bool Filter(BaseItem item, User user, InternalItemsQuery query, IUserDataManager userDataManager, ILibraryManager libraryManager) { - if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { return false; } @@ -785,7 +783,7 @@ namespace MediaBrowser.Controller.Entities } // Apply genre filter - if (query.Genres.Count > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))) + if (query.Genres.Count > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparison.OrdinalIgnoreCase))) { return false; } @@ -809,7 +807,7 @@ namespace MediaBrowser.Controller.Entities if (query.StudioIds.Length > 0 && !query.StudioIds.Any(id => { var studioItem = libraryManager.GetItemById(id); - return studioItem != null && item.Studios.Contains(studioItem.Name, StringComparer.OrdinalIgnoreCase); + return studioItem != null && item.Studios.Contains(studioItem.Name, StringComparison.OrdinalIgnoreCase); })) { return false; @@ -819,7 +817,7 @@ namespace MediaBrowser.Controller.Entities if (query.GenreIds.Count > 0 && !query.GenreIds.Any(id => { var genreItem = libraryManager.GetItemById(id); - return genreItem != null && item.Genres.Contains(genreItem.Name, StringComparer.OrdinalIgnoreCase); + return genreItem != null && item.Genres.Contains(genreItem.Name, StringComparison.OrdinalIgnoreCase); })) { return false; @@ -852,7 +850,7 @@ namespace MediaBrowser.Controller.Entities var tags = query.Tags; if (tags.Length > 0) { - if (!tags.Any(v => item.Tags.Contains(v, StringComparer.OrdinalIgnoreCase))) + if (!tags.Any(v => item.Tags.Contains(v, StringComparison.OrdinalIgnoreCase))) { return false; } @@ -970,7 +968,7 @@ namespace MediaBrowser.Controller.Entities { var folder = i as ICollectionFolder; - return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase); }).ToArray(); } @@ -979,7 +977,7 @@ namespace MediaBrowser.Controller.Entities { var folder = i as ICollectionFolder; - return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase); }).ToArray(); } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 8e0593507a..4f7614f962 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; @@ -192,7 +193,7 @@ namespace MediaBrowser.Controller.Entities { if (SourceType == SourceType.Channel) { - return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase); + return !Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase); } return !IsActiveRecording(); diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 8f8cf75a6c..75ec5f213f 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -2,7 +2,6 @@ #pragma warning disable CS1591 -using System.Collections.Generic; using System.Net; using MediaBrowser.Common; using MediaBrowser.Model.System; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 074e023e8d..2997731001 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -5,9 +5,9 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value> [JsonIgnore] - public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase); + public bool IsKids => Tags.Contains("Kids", StringComparison.OrdinalIgnoreCase); [JsonIgnore] public bool IsRepeat { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 111dc0d275..6c4a5ea177 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; @@ -66,7 +67,7 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value> [JsonIgnore] - public bool IsSports => Tags.Contains("Sports", StringComparer.OrdinalIgnoreCase); + public bool IsSports => Tags.Contains("Sports", StringComparison.OrdinalIgnoreCase); /// <summary> /// Gets or sets a value indicating whether this instance is series. @@ -80,28 +81,28 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value> [JsonIgnore] - public bool IsLive => Tags.Contains("Live", StringComparer.OrdinalIgnoreCase); + public bool IsLive => Tags.Contains("Live", StringComparison.OrdinalIgnoreCase); /// <summary> /// Gets a value indicating whether this instance is news. /// </summary> /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value> [JsonIgnore] - public bool IsNews => Tags.Contains("News", StringComparer.OrdinalIgnoreCase); + public bool IsNews => Tags.Contains("News", StringComparison.OrdinalIgnoreCase); /// <summary> /// Gets a value indicating whether this instance is kids. /// </summary> /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value> [JsonIgnore] - public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase); + public bool IsKids => Tags.Contains("Kids", StringComparison.OrdinalIgnoreCase); /// <summary> /// Gets a value indicating whether this instance is premiere. /// </summary> /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value> [JsonIgnore] - public bool IsPremiere => Tags.Contains("Premiere", StringComparer.OrdinalIgnoreCase); + public bool IsPremiere => Tags.Contains("Premiere", StringComparison.OrdinalIgnoreCase); /// <summary> /// Gets the folder containing the item. diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index 1a2e8acb31..62541ea8bf 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Extensions; using MediaBrowser.Model.LiveTv; namespace MediaBrowser.Controller.LiveTv @@ -123,11 +123,11 @@ namespace MediaBrowser.Controller.LiveTv public bool IsMovie { get; set; } - public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase); + public bool IsKids => Tags.Contains("Kids", StringComparison.OrdinalIgnoreCase); - public bool IsSports => Tags.Contains("Sports", StringComparer.OrdinalIgnoreCase); + public bool IsSports => Tags.Contains("Sports", StringComparison.OrdinalIgnoreCase); - public bool IsNews => Tags.Contains("News", StringComparer.OrdinalIgnoreCase); + public bool IsNews => Tags.Contains("News", StringComparison.OrdinalIgnoreCase); public bool IsSeries { get; set; } @@ -136,10 +136,10 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value> [JsonIgnore] - public bool IsLive => Tags.Contains("Live", StringComparer.OrdinalIgnoreCase); + public bool IsLive => Tags.Contains("Live", StringComparison.OrdinalIgnoreCase); [JsonIgnore] - public bool IsPremiere => Tags.Contains("Premiere", StringComparer.OrdinalIgnoreCase); + public bool IsPremiere => Tags.Contains("Premiere", StringComparison.OrdinalIgnoreCase); public int? ProductionYear { get; set; } diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index a42c7f8b5e..90fd6e269d 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -4,6 +4,7 @@ using System; using System.Linq; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Providers; @@ -58,7 +59,7 @@ namespace MediaBrowser.Controller.Providers { if (RefreshPaths != null && RefreshPaths.Length > 0) { - return RefreshPaths.Contains(item.Path ?? string.Empty, StringComparer.OrdinalIgnoreCase); + return RefreshPaths.Contains(item.Path ?? string.Empty, StringComparison.OrdinalIgnoreCase); } return true; diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs index 6d076ba273..d3fa41bcdf 100644 --- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index da76ff0f98..9057a101ab 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1362,8 +1362,8 @@ namespace MediaBrowser.MediaEncoding.Probing } // Don't add artist/album artist name to studios, even if it's listed there - if (info.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase) - || info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase)) + if (info.Artists.Contains(studio, StringComparison.OrdinalIgnoreCase) + || info.AlbumArtists.Contains(studio, StringComparison.OrdinalIgnoreCase)) { continue; } diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs index 8343cf028b..f857bf3a8c 100644 --- a/MediaBrowser.Model/Dlna/CodecProfile.cs +++ b/MediaBrowser.Model/Dlna/CodecProfile.cs @@ -2,8 +2,8 @@ #pragma warning disable CS1591 using System; -using System.Linq; using System.Xml.Serialization; +using Jellyfin.Extensions; namespace MediaBrowser.Model.Dlna { @@ -58,7 +58,7 @@ namespace MediaBrowser.Model.Dlna foreach (var val in codec) { - if (codecs.Contains(val, StringComparer.OrdinalIgnoreCase)) + if (codecs.Contains(val, StringComparison.OrdinalIgnoreCase)) { return true; } diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index 55c4dd0742..8d03b4c0b3 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -2,7 +2,7 @@ using System; using System.Globalization; -using System.Linq; +using Jellyfin.Extensions; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna @@ -167,7 +167,7 @@ namespace MediaBrowser.Model.Dlna switch (condition.Condition) { case ProfileConditionType.EqualsAny: - return expected.Split('|').Contains(currentValue, StringComparer.OrdinalIgnoreCase); + return expected.Split('|').Contains(currentValue, StringComparison.OrdinalIgnoreCase); case ProfileConditionType.Equals: return string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase); case ProfileConditionType.NotEquals: diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index 7409660881..c6befdd857 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 using System; -using System.Linq; using System.Xml.Serialization; +using Jellyfin.Extensions; namespace MediaBrowser.Model.Dlna { @@ -62,7 +62,7 @@ namespace MediaBrowser.Model.Dlna foreach (var container in allInputContainers) { - if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) + if (profileContainers.Contains(container, StringComparison.OrdinalIgnoreCase)) { return !isNegativeList; } diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index feb3d880ec..6170ff5bd6 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,8 +1,8 @@ #pragma warning disable CA1819 // Properties should not return arrays using System; using System.ComponentModel; -using System.Linq; using System.Xml.Serialization; +using Jellyfin.Extensions; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna @@ -253,7 +253,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -287,7 +287,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -328,7 +328,7 @@ namespace MediaBrowser.Model.Dlna } var audioCodecs = i.GetAudioCodecs(); - if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -469,13 +469,13 @@ namespace MediaBrowser.Model.Dlna } var audioCodecs = i.GetAudioCodecs(); - if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } var videoCodecs = i.GetVideoCodecs(); - if (videoCodecs.Length > 0 && !videoCodecs.Contains(videoCodec ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (videoCodecs.Length > 0 && !videoCodecs.Contains(videoCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } diff --git a/MediaBrowser.Model/Dlna/SubtitleProfile.cs b/MediaBrowser.Model/Dlna/SubtitleProfile.cs index 01e3c696b5..9ebde25ffe 100644 --- a/MediaBrowser.Model/Dlna/SubtitleProfile.cs +++ b/MediaBrowser.Model/Dlna/SubtitleProfile.cs @@ -2,8 +2,8 @@ #pragma warning disable CS1591 using System; -using System.Linq; using System.Xml.Serialization; +using Jellyfin.Extensions; namespace MediaBrowser.Model.Dlna { @@ -42,7 +42,7 @@ namespace MediaBrowser.Model.Dlna } var languages = GetLanguages(); - return languages.Length == 0 || languages.Contains(subLanguage, StringComparer.OrdinalIgnoreCase); + return languages.Length == 0 || languages.Contains(subLanguage, StringComparison.OrdinalIgnoreCase); } } } diff --git a/MediaBrowser.Model/Entities/ImageType.cs b/MediaBrowser.Model/Entities/ImageType.cs index 684b7390a6..1f7e03718d 100644 --- a/MediaBrowser.Model/Entities/ImageType.cs +++ b/MediaBrowser.Model/Entities/ImageType.cs @@ -1,5 +1,3 @@ -using System; - namespace MediaBrowser.Model.Entities { /// <summary> diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index 09beb2ef72..d1b5491bd1 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -2,9 +2,9 @@ #pragma warning disable CS1591 using System; -using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; namespace MediaBrowser.Model.Notifications { @@ -94,7 +94,7 @@ namespace MediaBrowser.Model.Notifications NotificationOption opt = GetOptions(notificationType); return opt == null - || !opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase); + || !opt.DisabledServices.Contains(service, StringComparison.OrdinalIgnoreCase); } public bool IsEnabledToMonitorUser(string type, Guid userId) @@ -103,7 +103,7 @@ namespace MediaBrowser.Model.Notifications return opt != null && opt.Enabled - && !opt.DisabledMonitorUsers.Contains(userId.ToString("N"), StringComparer.OrdinalIgnoreCase); + && !opt.DisabledMonitorUsers.Contains(userId.ToString("N"), StringComparison.OrdinalIgnoreCase); } public bool IsEnabledToSendToUser(string type, string userId, User user) @@ -122,7 +122,7 @@ namespace MediaBrowser.Model.Notifications return true; } - return opt.SendToUsers.Contains(userId, StringComparer.OrdinalIgnoreCase); + return opt.SendToUsers.Contains(userId, StringComparison.OrdinalIgnoreCase); } return false; diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index d2a3344bef..4632e1d511 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -173,7 +174,7 @@ namespace MediaBrowser.Providers.Manager // Delete the current path if (currentImageIsLocalFile - && !savedPaths.Contains(currentImagePath, StringComparer.OrdinalIgnoreCase) + && !savedPaths.Contains(currentImagePath, StringComparison.OrdinalIgnoreCase) && (saveLocally || currentImagePath.Contains(_config.ApplicationPaths.InternalMetadataPath, StringComparison.OrdinalIgnoreCase))) { var currentPath = currentImagePath; @@ -494,7 +495,7 @@ namespace MediaBrowser.Providers.Manager var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList(); var current = 1; - while (filenames.Contains(numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + while (filenames.Contains(numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)) { current++; } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 6e57533618..0385ce6a76 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -13,6 +13,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; @@ -660,7 +661,7 @@ namespace MediaBrowser.Providers.Manager /// <inheritdoc/> public void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers) { - SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparer.OrdinalIgnoreCase))); + SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparison.OrdinalIgnoreCase))); } /// <summary> @@ -737,7 +738,7 @@ namespace MediaBrowser.Providers.Manager { if (libraryOptions.MetadataSavers == null) { - if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparer.OrdinalIgnoreCase)) + if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase)) { return false; } @@ -763,7 +764,7 @@ namespace MediaBrowser.Providers.Manager } else { - if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparer.OrdinalIgnoreCase)) + if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase)) { return false; } diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index 8baca30c65..fe9986d424 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; @@ -44,7 +45,7 @@ namespace MediaBrowser.Providers.Playlists } var extension = Path.GetExtension(path); - if (!Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (!Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { return Task.FromResult(ItemUpdateType.None); } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs index 4c3fc23b2b..60b373483f 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs @@ -2,7 +2,6 @@ #pragma warning disable CS1591 -using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index fcaacc90d1..e4a56fde94 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -279,8 +280,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies // Normalize this var type = TmdbUtils.MapCrewToPersonType(person); - if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) && - !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (!keepTypes.Contains(type, StringComparison.OrdinalIgnoreCase) && + !keepTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index 8ac9d0cab1..f50f158772 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -188,8 +189,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // Normalize this var type = TmdbUtils.MapCrewToPersonType(person); - if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparer.OrdinalIgnoreCase) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparison.OrdinalIgnoreCase) + && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 7afaddc245..27c52a5a22 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -87,8 +88,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // Normalize this var type = TmdbUtils.MapCrewToPersonType(person); - if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparer.OrdinalIgnoreCase) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparison.OrdinalIgnoreCase) + && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 77e22ffbf0..f565b65698 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -365,8 +366,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // Normalize this var type = TmdbUtils.MapCrewToPersonType(person); - if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) - && !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (!keepTypes.Contains(type, StringComparison.OrdinalIgnoreCase) + && !keepTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index c2b420c331..34019e5824 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -76,8 +77,7 @@ namespace MediaBrowser.Providers.Subtitles var contentType = request.ContentType; var providers = _subtitleProviders - .Where(i => i.SupportedMediaTypes.Contains(contentType)) - .Where(i => !request.DisabledSubtitleFetchers.Contains(i.Name, StringComparer.OrdinalIgnoreCase)) + .Where(i => i.SupportedMediaTypes.Contains(contentType) && !request.DisabledSubtitleFetchers.Contains(i.Name, StringComparison.OrdinalIgnoreCase)) .OrderBy(i => { var index = request.SubtitleFetcherOrder.ToList().IndexOf(i.Name); diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 594402258a..d099813040 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -9,6 +9,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Xml; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -1003,7 +1004,7 @@ namespace MediaBrowser.XbmcMetadata.Savers var name = reader.Name; if (!_commonTags.Contains(name) - && !xmlTagsUsed.Contains(name, StringComparer.OrdinalIgnoreCase)) + && !xmlTagsUsed.Contains(name, StringComparison.OrdinalIgnoreCase)) { writer.WriteNode(reader, false); } diff --git a/RSSDP/HttpRequestParser.cs b/RSSDP/HttpRequestParser.cs index 4114195a63..a3e100796d 100644 --- a/RSSDP/HttpRequestParser.cs +++ b/RSSDP/HttpRequestParser.cs @@ -1,6 +1,6 @@ using System; -using System.Linq; using System.Net.Http; +using Jellyfin.Extensions; namespace Rssdp.Infrastructure { @@ -86,7 +86,7 @@ namespace Rssdp.Infrastructure /// <param name="headerName">A string containing the name of the header to return the type of.</param> protected override bool IsContentHeader(string headerName) { - return ContentHeaderNames.Contains(headerName, StringComparer.OrdinalIgnoreCase); + return ContentHeaderNames.Contains(headerName, StringComparison.OrdinalIgnoreCase); } } } diff --git a/RSSDP/HttpResponseParser.cs b/RSSDP/HttpResponseParser.cs index 0dd4bb45a8..3e361465d7 100644 --- a/RSSDP/HttpResponseParser.cs +++ b/RSSDP/HttpResponseParser.cs @@ -1,7 +1,7 @@ using System; -using System.Linq; using System.Net; using System.Net.Http; +using Jellyfin.Extensions; namespace Rssdp.Infrastructure { @@ -49,7 +49,7 @@ namespace Rssdp.Infrastructure /// <returns>A boolean, true if th specified header relates to HTTP content, otherwise false.</returns> protected override bool IsContentHeader(string headerName) { - return ContentHeaderNames.Contains(headerName, StringComparer.OrdinalIgnoreCase); + return ContentHeaderNames.Contains(headerName, StringComparison.OrdinalIgnoreCase); } /// <summary> diff --git a/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs b/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs index edceef4a7c..463e17ad36 100644 --- a/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs +++ b/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Model.Configuration; using Moq; using Xunit; diff --git a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs index d18441ac01..7abd2e685f 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs @@ -6,9 +6,7 @@ using System.Threading; using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; -using Emby.Server.Implementations.Archiving; using Emby.Server.Implementations.Updates; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Updates; using Moq; using Moq.Protected; diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs index 4c8f64d1e9..21131eb97f 100644 --- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs +++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs @@ -3,7 +3,6 @@ using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Net.Http.Headers; -using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models.StartupDtos; diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs index 4c46933aab..5d7b0e874e 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs @@ -1,9 +1,7 @@ using System; using System.Linq; using System.Net; -using System.Net.Http; using System.Net.Http.Json; -using System.Net.Http.Headers; using System.Net.Mime; using System.Text; using System.Text.Json; diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs index 2da5237db2..24251013c5 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs @@ -2,8 +2,6 @@ using System; using System.Net; using System.Net.Http; using System.Net.Http.Json; -using System.Net.Http.Headers; -using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models.LibraryStructureDto; diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs index ed92ce25a4..e72dacfe0f 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs @@ -2,7 +2,6 @@ using System; using System.Net; using System.Net.Http; using System.Net.Http.Json; -using System.Net.Http.Headers; using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs index f11f276f80..588e25a82d 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs @@ -4,8 +4,6 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Json; -using System.Net.Http.Headers; -using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models.UserDtos; From afaff1310f7c2a9763a00ede7927d3133407f3d3 Mon Sep 17 00:00:00 2001 From: Kichirou Hoshino <devel.kp6ns@aleeas.com> Date: Mon, 20 Dec 2021 06:13:33 +0000 Subject: [PATCH 158/167] Translated using Weblate (Filipino) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fil/ --- Emby.Server.Implementations/Localization/Core/fil.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json index f18a1c0303..99839ae6e8 100644 --- a/Emby.Server.Implementations/Localization/Core/fil.json +++ b/Emby.Server.Implementations/Localization/Core/fil.json @@ -117,5 +117,7 @@ "TaskCleanActivityLogDescription": "Tanggalin ang mga tala ng aktibidad na mas luma sa nakatakda na edad.", "Default": "Default", "Undefined": "Hindi tiyak", - "Forced": "Sapilitan" + "Forced": "Sapilitan", + "TaskOptimizeDatabaseDescription": "Iko-compact ang database at ita-truncate ang free space. Ang pagpapatakbo ng gawaing ito pagkatapos ng pag-scan sa library o paggawa ng iba pang mga pagbabago na nagpapahiwatig ng mga pagbabago sa database ay maaaring magpa-improve ng performance.", + "TaskOptimizeDatabase": "I-optimize ang database" } From 915851101746ed18a1767fa617c52f174732a6f6 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Mon, 20 Dec 2021 23:58:09 +0100 Subject: [PATCH 159/167] Don't skip extras refresh when replacing metadata or doing a full refresh --- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index cb21add125..a216a3b600 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1385,7 +1385,7 @@ namespace MediaBrowser.Controller.Entities var newExtraIds = extras.Select(i => i.Id).ToArray(); var extrasChanged = !item.ExtraIds.SequenceEqual(newExtraIds); - if (!extrasChanged) + if (!extrasChanged && !options.ReplaceAllMetadata && options.MetadataRefreshMode != MetadataRefreshMode.FullRefresh) { return false; } From 05c8834a3a2a51ad2c0bff355500348382697fb5 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Tue, 21 Dec 2021 00:10:58 +0100 Subject: [PATCH 160/167] Don't cache special feature ids --- .../Entities/IHasSpecialFeatures.cs | 4 ++-- .../Entities/Movies/Movie.cs | 21 ++++--------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs b/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs index f317a02ff3..f47d2162f7 100644 --- a/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs +++ b/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs @@ -10,9 +10,9 @@ namespace MediaBrowser.Controller.Entities public interface IHasSpecialFeatures { /// <summary> - /// Gets or sets the special feature ids. + /// Gets the special feature ids. /// </summary> /// <value>The special feature ids.</value> - IReadOnlyList<Guid> SpecialFeatureIds { get; set; } + IReadOnlyList<Guid> SpecialFeatureIds { get; } } } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 6f1a0a8cfe..dfaf03fdad 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -19,24 +19,11 @@ namespace MediaBrowser.Controller.Entities.Movies /// </summary> public class Movie : Video, IHasSpecialFeatures, IHasTrailers, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping { - private IReadOnlyList<Guid> _specialFeatureIds; - /// <inheritdoc /> - public IReadOnlyList<Guid> SpecialFeatureIds - { - get - { - return _specialFeatureIds ??= GetExtras() - .Where(extra => extra.ExtraType != Model.Entities.ExtraType.Trailer) - .Select(song => song.Id) - .ToArray(); - } - - set - { - _specialFeatureIds = value; - } - } + public IReadOnlyList<Guid> SpecialFeatureIds => GetExtras() + .Where(extra => extra.ExtraType != null && extra is Video) + .Select(extra => extra.Id) + .ToArray(); /// <inheritdoc /> public IReadOnlyList<BaseItem> LocalTrailers => GetExtras() From c86b064f80eecaf8dcc0886105cd248429373fef Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Tue, 21 Dec 2021 12:29:09 +0100 Subject: [PATCH 161/167] Catch HttpRequestException when saving images from local provider --- MediaBrowser.Providers/Manager/MetadataService.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 90d14a9731..0af76f75a0 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; @@ -679,8 +680,15 @@ namespace MediaBrowser.Providers.Manager { foreach (var remoteImage in localItem.RemoteImages) { - await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false); - refreshResult.UpdateType |= ItemUpdateType.ImageUpdate; + try + { + await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false); + refreshResult.UpdateType |= ItemUpdateType.ImageUpdate; + } + catch (HttpRequestException ex) + { + Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.type), remoteImage.url); + } } if (imageService.MergeImages(item, localItem.Images)) From a7a7173cd5440bb1ef930ae631fd91f684de0d5f Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Tue, 21 Dec 2021 14:35:58 +0100 Subject: [PATCH 162/167] Force a remux/transcode with external audio files --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 16 +++++++++++++--- MediaBrowser.Model/Session/TranscodeReason.cs | 3 ++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 322cc367b6..e0ca885ccf 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -677,8 +677,8 @@ namespace MediaBrowser.Model.Dlna var videoStream = item.VideoStream; // TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough - var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, options, PlayMethod.DirectPlay); - var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, options, PlayMethod.DirectStream); + var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectPlay); + var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectStream); bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1); bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.Item1); @@ -1213,6 +1213,7 @@ namespace MediaBrowser.Model.Dlna MediaSourceInfo item, long maxBitrate, MediaStream subtitleStream, + MediaStream audioStream, VideoOptions options, PlayMethod playMethod) { @@ -1228,8 +1229,17 @@ namespace MediaBrowser.Model.Dlna } bool result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod); + if (!result) + { + return (false, TranscodeReason.ContainerBitrateExceedsLimit); + } - return (result, result ? null : TranscodeReason.ContainerBitrateExceedsLimit); + if (audioStream.IsExternal) + { + return (false, TranscodeReason.AudioIsExternal); + } + + return (true, null); } public static SubtitleProfile GetSubtitleProfile( diff --git a/MediaBrowser.Model/Session/TranscodeReason.cs b/MediaBrowser.Model/Session/TranscodeReason.cs index e93b5d2882..3c95df66d5 100644 --- a/MediaBrowser.Model/Session/TranscodeReason.cs +++ b/MediaBrowser.Model/Session/TranscodeReason.cs @@ -26,6 +26,7 @@ namespace MediaBrowser.Model.Session VideoProfileNotSupported = 19, AudioBitDepthNotSupported = 20, SubtitleCodecNotSupported = 21, - DirectPlayError = 22 + DirectPlayError = 22, + AudioIsExternal = 23 } } From 7774cb206733902edb553fce0306b3ece5b65fb4 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Thu, 23 Dec 2021 20:46:27 +0000 Subject: [PATCH 163/167] Update Jellyfin.Api/Controllers/LibraryController.cs --- Jellyfin.Api/Controllers/LibraryController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index b3c6fac7c8..9d910386a1 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -739,7 +739,7 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - Genres = item.Genres, // Passing items' genres to obtain a more accurate list of its related media + Genres = item.Genres, Limit = limit, IncludeItemTypes = includeItemTypes.ToArray(), SimilarTo = item, From a8a8ce4e7b1ef137cc176721d32642e085837701 Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Thu, 23 Dec 2021 19:27:51 -0700 Subject: [PATCH 164/167] Fix build from PR merging --- MediaBrowser.Controller/Entities/BaseItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 3ef2e5192e..95d49508fe 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Diacritics.Extensions; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; From 932c2c6665bddfc284283fb1a6f28bf5fc25dcee Mon Sep 17 00:00:00 2001 From: Cody Robibero <cody@robibe.ro> Date: Thu, 23 Dec 2021 19:40:24 -0700 Subject: [PATCH 165/167] Fix config.html --- .../Plugins/StudioImages/Configuration/config.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html index 5ede1a2bbf..f9fe3dc2e9 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <html> <head> - <title>AudioDB + Studio Images
From 17f43c8e01a764d8e030a75b8d3c7f89146677d8 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 24 Dec 2021 02:42:43 +0000 Subject: [PATCH 166/167] Update Jellyfin.Api/Controllers/VideoHlsController.cs --- Jellyfin.Api/Controllers/VideoHlsController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index bdd2612d1e..36643e464c 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -366,8 +366,7 @@ namespace Jellyfin.Api.Controllers else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)) { string outputFmp4HeaderArg; - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - if (isWindows) + if (OperatingSystem.IsWindows()) { // on Windows, the path of fmp4 header file needs to be configured outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\""; From ec2645c0c05685398c24238e6ec1e8c082d099ae Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 24 Dec 2021 16:35:57 +0100 Subject: [PATCH 167/167] Fix build --- MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs index 69a0569e5c..e0ab31b56e 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -11,6 +12,9 @@ namespace MediaBrowser.Providers.Plugins.StudioImages { public class Plugin : BasePlugin, IHasWebPages { + // TODO change this for a Jellyfin-hosted repository. + public const string DefaultServer = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname"; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { @@ -25,9 +29,6 @@ namespace MediaBrowser.Providers.Plugins.StudioImages public override string Description => "Get artwork for studios from any Jellyfin-compatible repository."; - // TODO change this for a Jellyfin-hosted repository. - public const string DefaultServer = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname"; - // TODO remove when plugin removed from server. public override string ConfigurationFileName => "Jellyfin.Plugin.StudioImages.xml";