aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-package.yml12
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs6
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs12
-rw-r--r--Emby.Dlna/DlnaManager.cs16
-rw-r--r--Emby.Dlna/PlayTo/Device.cs6
-rw-r--r--Emby.Dlna/Service/BaseControlHandler.cs49
-rw-r--r--Emby.Drawing/ImageProcessor.cs19
-rw-r--r--Emby.Naming/Common/NamingOptions.cs118
-rw-r--r--Emby.Naming/TV/SeasonPathParser.cs6
-rw-r--r--Emby.Naming/Video/ExtraRuleResolver.cs (renamed from Emby.Naming/Video/ExtraResolver.cs)65
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs7
-rw-r--r--Emby.Naming/Video/VideoResolver.cs2
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs8
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs165
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs11
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketManager.cs6
-rw-r--r--Emby.Server.Implementations/IO/StreamHelper.cs20
-rw-r--r--Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs4
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs110
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs7
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs93
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs24
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/VideoExtraResolver.cs55
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs37
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs8
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs48
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs14
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/el.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/gsw.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/he.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/lt-LT.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ml.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/my.json123
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/sr.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/th.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/ur_PK.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/vi.json2
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs2
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs37
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs6
-rw-r--r--Emby.Server.Implementations/Udp/UdpServer.cs12
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs6
-rw-r--r--Jellyfin.Api/Controllers/DlnaServerController.cs7
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs8
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs11
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs6
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs4
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs14
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj2
-rw-r--r--Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs4
-rw-r--r--MediaBrowser.Common/Net/IPNetAddress.cs2
-rw-r--r--MediaBrowser.Common/Net/IPObject.cs2
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs4
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs1
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs2
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs19
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs1
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs2
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs14
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs104
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs6
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs2
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs3
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketConnection.cs6
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketListener.cs4
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs12
-rw-r--r--MediaBrowser.Controller/Providers/MetadataResult.cs6
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs10
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs28
-rw-r--r--MediaBrowser.Model/Drawing/ImageFormatExtensions.cs27
-rw-r--r--MediaBrowser.Model/Session/GeneralCommandType.cs4
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs15
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs10
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html6
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs5
-rw-r--r--MediaBrowser.Providers/TV/SeasonMetadataService.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs2
-rw-r--r--README.md10
-rw-r--r--fedora/Makefile10
-rw-r--r--jellyfin.ruleset6
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj2
-rw-r--r--tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj2
-rw-r--r--tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs33
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs7
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs7
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs7
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs15
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs27
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs7
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs5
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs5
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs5
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs42
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs4
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs8
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs105
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs8
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs33
122 files changed, 1051 insertions, 877 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 89f7137fd..19d65ea0c 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -43,10 +43,6 @@ 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')
-
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile'
@@ -92,10 +88,6 @@ 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: DownloadPipelineArtifact@2
displayName: 'Download OpenAPI Spec'
inputs:
@@ -143,10 +135,6 @@ 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/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 010f90c62..0cd1a0daf 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -1192,13 +1192,13 @@ namespace Emby.Dlna.ContentDirectory
/// </summary>
/// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param>
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
- private static QueryResult<ServerItem> ToResult(QueryResult<(BaseItem, ItemCounts)> result)
+ private static QueryResult<ServerItem> ToResult(QueryResult<(BaseItem Item, ItemCounts ItemCounts)> result)
{
var length = result.Items.Count;
var serverItems = new ServerItem[length];
for (var i = 0; i < length; i++)
{
- serverItems[i] = new ServerItem(result.Items[i].Item1, null);
+ serverItems[i] = new ServerItem(result.Items[i].Item, null);
}
return new QueryResult<ServerItem>
@@ -1213,7 +1213,7 @@ namespace Emby.Dlna.ContentDirectory
/// </summary>
/// <param name="sort">The <see cref="SortCriteria"/>.</param>
/// <param name="isPreSorted">True if pre-sorted.</param>
- private static (string, SortOrder)[] GetOrderBy(SortCriteria sort, bool isPreSorted)
+ private static (string SortName, SortOrder SortOrder)[] GetOrderBy(SortCriteria sort, bool isPreSorted)
{
return isPreSorted ? Array.Empty<(string, SortOrder)>() : new[] { (ItemSortBy.SortName, sort.SortOrder) };
}
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index b00e1c98a..6803b3b87 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -989,7 +989,7 @@ namespace Emby.Dlna.Didl
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
}
- writer.WriteString(albumArtUrlInfo.url);
+ writer.WriteString(albumArtUrlInfo.Url);
writer.WriteFullEndElement();
// TODO: Remove these default values
@@ -998,7 +998,7 @@ namespace Emby.Dlna.Didl
_profile.MaxIconWidth ?? 48,
_profile.MaxIconHeight ?? 48,
"jpg");
- writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
+ writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.Url);
if (!_profile.EnableAlbumArtInDidl)
{
@@ -1045,8 +1045,8 @@ namespace Emby.Dlna.Didl
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
// rather than using a larger one when available
- var width = albumartUrlInfo.width ?? maxWidth;
- var height = albumartUrlInfo.height ?? maxHeight;
+ var width = albumartUrlInfo.Width ?? maxWidth;
+ var height = albumartUrlInfo.Height ?? maxHeight;
var contentFeatures = ContentFeatureBuilder.BuildImageHeader(_profile, format, width, height, imageInfo.IsDirectStream, org_Pn);
@@ -1062,7 +1062,7 @@ namespace Emby.Dlna.Didl
"resolution",
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
- writer.WriteString(albumartUrlInfo.url);
+ writer.WriteString(albumartUrlInfo.Url);
writer.WriteFullEndElement();
}
@@ -1200,7 +1200,7 @@ namespace Emby.Dlna.Didl
return id;
}
- private (string url, int? width, int? height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
+ private (string Url, int? Width, int? Height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
{
var url = string.Format(
CultureInfo.InvariantCulture,
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index d9d2a345a..f2a0548c2 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -83,8 +83,7 @@ namespace Emby.Dlna
{
lock (_profiles)
{
- var list = _profiles.Values.ToList();
- return list
+ return _profiles.Values
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Item1.Info.Name)
.Select(i => i.Item2)
@@ -226,11 +225,8 @@ namespace Emby.Dlna
{
try
{
- var xmlFies = _fileSystem.GetFilePaths(path)
+ return _fileSystem.GetFilePaths(path)
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
- .ToList();
-
- return xmlFies
.Select(i => ParseProfileFile(i, type))
.Where(i => i != null)
.ToList()!; // We just filtered out all the nulls
@@ -252,11 +248,8 @@ namespace Emby.Dlna
try
{
- DeviceProfile profile;
-
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
-
- profile = ReserializeProfile(tempProfile);
+ var profile = ReserializeProfile(tempProfile);
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
@@ -295,8 +288,7 @@ namespace Emby.Dlna
{
lock (_profiles)
{
- var list = _profiles.Values.ToList();
- return list
+ return _profiles.Values
.Select(i => i.Item1)
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Info.Name);
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 34fb8fddd..7815e9293 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -535,9 +535,9 @@ namespace Emby.Dlna.PlayTo
{
var tuple = await GetPositionInfo(avCommands, cancellationToken).ConfigureAwait(false);
- var currentObject = tuple.Item2;
+ var currentObject = tuple.Track;
- if (tuple.Item1 && currentObject == null)
+ if (tuple.Success && currentObject == null)
{
currentObject = await GetMediaInfo(avCommands, cancellationToken).ConfigureAwait(false);
}
@@ -797,7 +797,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- private async Task<(bool, UBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
+ private async Task<(bool Success, UBaseObject Track)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
{
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
if (command == null)
diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs
index 780aad9c1..7bec2eb72 100644
--- a/Emby.Dlna/Service/BaseControlHandler.cs
+++ b/Emby.Dlna/Service/BaseControlHandler.cs
@@ -47,7 +47,7 @@ namespace Emby.Dlna.Service
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
{
- ControlRequestInfo? requestInfo = null;
+ ControlRequestInfo requestInfo;
using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
{
@@ -66,6 +66,11 @@ namespace Emby.Dlna.Service
Logger.LogDebug("Received control request {LocalName}, params: {@Headers}", requestInfo.LocalName, requestInfo.Headers);
+ return CreateControlResponse(requestInfo);
+ }
+
+ private ControlResponse CreateControlResponse(ControlRequestInfo requestInfo)
+ {
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
@@ -112,29 +117,19 @@ namespace Emby.Dlna.Service
{
if (reader.NodeType == XmlNodeType.Element)
{
- switch (reader.LocalName)
+ if (string.Equals(reader.LocalName, "Body", StringComparison.Ordinal))
{
- case "Body":
- {
- if (!reader.IsEmptyElement)
- {
- using var subReader = reader.ReadSubtree();
- return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
- }
- else
- {
- await reader.ReadAsync().ConfigureAwait(false);
- }
-
- break;
- }
-
- default:
- {
- await reader.SkipAsync().ConfigureAwait(false);
- break;
- }
+ if (reader.IsEmptyElement)
+ {
+ await reader.ReadAsync().ConfigureAwait(false);
+ continue;
+ }
+
+ using var subReader = reader.ReadSubtree();
+ return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
}
+
+ await reader.SkipAsync().ConfigureAwait(false);
}
else
{
@@ -160,17 +155,17 @@ namespace Emby.Dlna.Service
localName = reader.LocalName;
namespaceURI = reader.NamespaceURI;
- if (!reader.IsEmptyElement)
+ if (reader.IsEmptyElement)
+ {
+ await reader.ReadAsync().ConfigureAwait(false);
+ }
+ else
{
var result = new ControlRequestInfo(localName, namespaceURI);
using var subReader = reader.ReadSubtree();
await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
return result;
}
- else
- {
- await reader.ReadAsync().ConfigureAwait(false);
- }
}
else
{
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index c9847aa2d..18b413964 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
@@ -101,7 +102,7 @@ namespace Emby.Drawing
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
{
var file = await ProcessImage(options).ConfigureAwait(false);
- using (var fileStream = AsyncFile.OpenRead(file.path))
+ using (var fileStream = AsyncFile.OpenRead(file.Path))
{
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
}
@@ -116,7 +117,7 @@ namespace Emby.Drawing
=> _transparentImageTypes.Contains(Path.GetExtension(path));
/// <inheritdoc />
- public async Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
+ public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options)
{
ItemImageInfo originalImage = options.Image;
BaseItem item = options.Item;
@@ -129,20 +130,22 @@ namespace Emby.Drawing
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
}
+ var mimeType = MimeTypes.GetMimeType(originalImagePath);
if (!_imageEncoder.SupportsImageEncoding)
{
- return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
+ return (originalImagePath, mimeType, dateModified);
}
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
- originalImagePath = supportedImageInfo.path;
+ originalImagePath = supportedImageInfo.Path;
- if (!File.Exists(originalImagePath))
+ // Original file doesn't exist, or original file is gif.
+ if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase))
{
- return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
+ return (originalImagePath, mimeType, dateModified);
}
- dateModified = supportedImageInfo.dateModified;
+ dateModified = supportedImageInfo.DateModified;
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
bool autoOrient = false;
@@ -436,7 +439,7 @@ namespace Emby.Drawing
.ToString("N", CultureInfo.InvariantCulture);
}
- private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
+ private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{
var inputFormat = Path.GetExtension(originalImagePath)
.TrimStart('.')
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index c0be0b7c6..e8c855b5a 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -411,6 +411,66 @@ namespace Emby.Naming.Common
MediaType.Video),
new ExtraRule(
+ ExtraType.ThemeVideo,
+ ExtraRuleType.DirectoryName,
+ "backdrops",
+ MediaType.Video),
+
+ new ExtraRule(
+ ExtraType.ThemeSong,
+ ExtraRuleType.DirectoryName,
+ "theme-music",
+ MediaType.Audio),
+
+ new ExtraRule(
+ ExtraType.BehindTheScenes,
+ ExtraRuleType.DirectoryName,
+ "behind the scenes",
+ MediaType.Video),
+
+ new ExtraRule(
+ ExtraType.DeletedScene,
+ ExtraRuleType.DirectoryName,
+ "deleted scenes",
+ MediaType.Video),
+
+ new ExtraRule(
+ ExtraType.Interview,
+ ExtraRuleType.DirectoryName,
+ "interviews",
+ MediaType.Video),
+
+ new ExtraRule(
+ ExtraType.Scene,
+ ExtraRuleType.DirectoryName,
+ "scenes",
+ MediaType.Video),
+
+ new ExtraRule(
+ ExtraType.Sample,
+ ExtraRuleType.DirectoryName,
+ "samples",
+ MediaType.Video),
+
+ new ExtraRule(
+ ExtraType.Clip,
+ ExtraRuleType.DirectoryName,
+ "shorts",
+ MediaType.Video),
+
+ new ExtraRule(
+ ExtraType.Clip,
+ ExtraRuleType.DirectoryName,
+ "featurettes",
+ MediaType.Video),
+
+ new ExtraRule(
+ ExtraType.Unknown,
+ ExtraRuleType.DirectoryName,
+ "extras",
+ MediaType.Video),
+
+ new ExtraRule(
ExtraType.Trailer,
ExtraRuleType.Filename,
"trailer",
@@ -471,24 +531,12 @@ namespace Emby.Naming.Common
MediaType.Video),
new ExtraRule(
- ExtraType.ThemeVideo,
- ExtraRuleType.DirectoryName,
- "backdrops",
- MediaType.Video),
-
- new ExtraRule(
ExtraType.ThemeSong,
ExtraRuleType.Filename,
"theme",
MediaType.Audio),
new ExtraRule(
- ExtraType.ThemeSong,
- ExtraRuleType.DirectoryName,
- "theme-music",
- MediaType.Audio),
-
- new ExtraRule(
ExtraType.Scene,
ExtraRuleType.Suffix,
"-scene",
@@ -537,51 +585,9 @@ namespace Emby.Naming.Common
MediaType.Video),
new ExtraRule(
- ExtraType.BehindTheScenes,
- ExtraRuleType.DirectoryName,
- "behind the scenes",
- MediaType.Video),
-
- new ExtraRule(
- ExtraType.DeletedScene,
- ExtraRuleType.DirectoryName,
- "deleted scenes",
- MediaType.Video),
-
- new ExtraRule(
- ExtraType.Interview,
- ExtraRuleType.DirectoryName,
- "interviews",
- MediaType.Video),
-
- new ExtraRule(
- ExtraType.Scene,
- ExtraRuleType.DirectoryName,
- "scenes",
- MediaType.Video),
-
- new ExtraRule(
- ExtraType.Sample,
- ExtraRuleType.DirectoryName,
- "samples",
- MediaType.Video),
-
- new ExtraRule(
- ExtraType.Clip,
- ExtraRuleType.DirectoryName,
- "shorts",
- MediaType.Video),
-
- new ExtraRule(
- ExtraType.Clip,
- ExtraRuleType.DirectoryName,
- "featurettes",
- MediaType.Video),
-
- new ExtraRule(
ExtraType.Unknown,
- ExtraRuleType.DirectoryName,
- "extras",
+ ExtraRuleType.Suffix,
+ "-extra",
MediaType.Video)
};
diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs
index 6236f86c4..fc9ee8e56 100644
--- a/Emby.Naming/TV/SeasonPathParser.cs
+++ b/Emby.Naming/TV/SeasonPathParser.cs
@@ -55,7 +55,7 @@ namespace Emby.Naming.TV
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
/// <returns>System.Nullable{System.Int32}.</returns>
- private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
+ private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath(
string path,
bool supportSpecialAliases,
bool supportNumericSeasonFolders)
@@ -99,7 +99,7 @@ namespace Emby.Naming.TV
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
{
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
- if (result.seasonNumber.HasValue)
+ if (result.SeasonNumber.HasValue)
{
return result;
}
@@ -142,7 +142,7 @@ namespace Emby.Naming.TV
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
- private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
+ private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
{
var numericStart = -1;
var length = 0;
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraRuleResolver.cs
index fbdca859f..0970e509a 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraRuleResolver.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Audio;
using Emby.Naming.Common;
@@ -11,7 +9,7 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolve if file is extra for video.
/// </summary>
- public static class ExtraResolver
+ public static class ExtraRuleResolver
{
private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
@@ -86,66 +84,5 @@ 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 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 = current.Files[0];
- var trimmedCurrentFileName = TrimFilenameDelimiters(currentFile.Name, videoFlagDelimiters);
-
- // first check filenames
- bool isValid = StartsWith(trimmedCurrentFileName, trimmedFileNameWithoutExtension)
- || (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/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 4fc849256..11f82525f 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -42,11 +42,14 @@ namespace Emby.Naming.Video
continue;
}
- remainingFiles.Add(current);
if (current.ExtraType == null)
{
standaloneMedia.Add(current);
}
+ else
+ {
+ remainingFiles.Add(current);
+ }
}
var list = new List<VideoInfo>();
@@ -69,8 +72,6 @@ namespace Emby.Naming.Video
var info = new VideoInfo(media.Name) { Files = new[] { media } };
info.Year = info.Files[0].Year;
-
- remainingFiles.Remove(media);
list.Add(info);
}
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index 9cadc1465..de8e177d8 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -75,7 +75,7 @@ namespace Emby.Naming.Video
var format3DResult = Format3DParser.Parse(path, namingOptions);
- var extraResult = ExtraResolver.GetExtraInfo(path, namingOptions);
+ var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions);
var name = Path.GetFileNameWithoutExtension(path);
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 8702691d1..43c8a451b 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -130,16 +130,14 @@ namespace Emby.Server.Implementations.Channels
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
if (internalChannel == null)
{
- throw new ArgumentException();
+ throw new ArgumentException(nameof(item.ChannelId));
}
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
- var supportsDelete = channel as ISupportsDelete;
-
- if (supportsDelete == null)
+ if (channel is not ISupportsDelete supportsDelete)
{
- throw new ArgumentException();
+ throw new ArgumentException(nameof(channel));
}
return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 79ef70fff..b5b8fea65 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Collections
if (parentFolder == null)
{
- throw new ArgumentException();
+ throw new ArgumentException(nameof(parentFolder));
}
var path = Path.Combine(parentFolder.Path, folderName);
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index a6b48b212..5ab9e02fe 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -248,40 +248,6 @@ namespace Emby.Server.Implementations.Data
BaseItemKind.AudioBook
};
- private static readonly Type[] _knownTypes =
- {
- typeof(LiveTvProgram),
- typeof(LiveTvChannel),
- typeof(Series),
- typeof(Audio),
- typeof(MusicAlbum),
- typeof(MusicArtist),
- typeof(MusicGenre),
- typeof(MusicVideo),
- typeof(Movie),
- typeof(Playlist),
- typeof(AudioBook),
- typeof(Trailer),
- typeof(BoxSet),
- typeof(Episode),
- typeof(Season),
- typeof(Series),
- typeof(Book),
- typeof(CollectionFolder),
- typeof(Folder),
- typeof(Genre),
- typeof(Person),
- typeof(Photo),
- typeof(PhotoAlbum),
- typeof(Studio),
- typeof(UserRootFolder),
- typeof(UserView),
- typeof(Video),
- typeof(Year),
- typeof(Channel),
- typeof(AggregateFolder)
- };
-
private static readonly Dictionary<BaseItemKind, string> _baseItemKindNames = new()
{
{ BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
@@ -688,13 +654,13 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction(
db =>
{
- SaveItemsInTranscation(db, tuples);
+ SaveItemsInTransaction(db, tuples);
},
TransactionMode);
}
}
- private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List<Guid>, BaseItem, string, List<string>)> tuples)
+ private void SaveItemsInTransaction(IDatabaseConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples)
{
var statements = PrepareAll(db, new string[]
{
@@ -713,17 +679,17 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.Reset();
}
- var item = tuple.Item1;
- var topParent = tuple.Item3;
- var userDataKey = tuple.Item4;
+ var item = tuple.Item;
+ var topParent = tuple.TopParent;
+ var userDataKey = tuple.UserDataKey;
SaveItem(item, topParent, userDataKey, saveItemStatement);
- var inheritedTags = tuple.Item5;
+ var inheritedTags = tuple.InheritedTags;
if (item.SupportsAncestors)
{
- UpdateAncestors(item.Id, tuple.Item2, db, deleteAncestorsStatement);
+ UpdateAncestors(item.Id, tuple.AncestorIds, db, deleteAncestorsStatement);
}
UpdateItemValues(item.Id, GetItemValuesToSave(item, inheritedTags), db);
@@ -2201,7 +2167,7 @@ namespace Emby.Server.Implementations.Data
return false;
}
- var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase);
+ var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.OrderBy), StringComparer.OrdinalIgnoreCase);
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
|| sortingFields.Contains(ItemSortBy.IsPlayed)
@@ -3049,88 +3015,101 @@ namespace Emby.Server.Implementations.Data
return " ORDER BY " + string.Join(',', orderBy.Select(i =>
{
- var columnMap = MapOrderByField(i.Item1, query);
-
- var sortOrder = i.Item2 == SortOrder.Ascending ? "ASC" : "DESC";
-
- return columnMap.Item1 + " " + sortOrder;
+ var sortBy = MapOrderByField(i.OrderBy, query);
+ var sortOrder = i.SortOrder == SortOrder.Ascending ? "ASC" : "DESC";
+ return sortBy + " " + sortOrder;
}));
}
- private (string, bool) MapOrderByField(string name, InternalItemsQuery query)
+ private string MapOrderByField(string name, InternalItemsQuery query)
{
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
{
// TODO
- return ("SortName", false);
+ return "SortName";
}
- else if (string.Equals(name, ItemSortBy.Runtime, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.Runtime, StringComparison.OrdinalIgnoreCase))
{
- return ("RuntimeTicks", false);
+ return "RuntimeTicks";
}
- else if (string.Equals(name, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
{
- return ("RANDOM()", false);
+ return "RANDOM()";
}
- else if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase))
{
if (query.GroupBySeriesPresentationUniqueKey)
{
- return ("MAX(LastPlayedDate)", false);
+ return "MAX(LastPlayedDate)";
}
- return ("LastPlayedDate", false);
+ return "LastPlayedDate";
}
- else if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
{
- return ("PlayCount", false);
+ return "PlayCount";
}
- else if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase))
{
- return ("(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", true);
+ return "(Select Case When IsFavorite is null Then 0 Else IsFavorite End )";
}
- else if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase))
{
- return ("IsFolder", true);
+ return "IsFolder";
}
- else if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase))
{
- return ("played", true);
+ return "played";
}
- else if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase))
{
- return ("played", false);
+ return "played";
}
- else if (string.Equals(name, ItemSortBy.DateLastContentAdded, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.DateLastContentAdded, StringComparison.OrdinalIgnoreCase))
{
- return ("DateLastMediaAdded", false);
+ return "DateLastMediaAdded";
}
- else if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
{
- return ("(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", false);
+ return "(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)";
}
- else if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
{
- return ("(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", false);
+ return "(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)";
}
- else if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
{
- return ("InheritedParentalRatingValue", false);
+ return "InheritedParentalRatingValue";
}
- else if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
{
- return ("(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)", false);
+ return "(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)";
}
- else if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
{
- return ("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)", false);
+ return "(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)";
}
- else if (string.Equals(name, ItemSortBy.SeriesSortName, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(name, ItemSortBy.SeriesSortName, StringComparison.OrdinalIgnoreCase))
{
- return ("SeriesName", false);
+ return "SeriesName";
}
- return (name, false);
+ return name;
}
public List<Guid> GetItemIdsList(InternalItemsQuery query)
@@ -5230,32 +5209,32 @@ AND Type = @InternalPersonType)");
}
}
- public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName);
}
@@ -5351,7 +5330,7 @@ AND Type = @InternalPersonType)");
return list;
}
- private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
+ private QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
{
if (query == null)
{
@@ -5676,7 +5655,7 @@ AND Type = @InternalPersonType)");
return counts;
}
- private List<(int, string)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags)
+ private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags)
{
var list = new List<(int, string)>();
@@ -5701,7 +5680,7 @@ AND Type = @InternalPersonType)");
return list;
}
- private void UpdateItemValues(Guid itemId, List<(int, string)> values, IDatabaseConnection db)
+ private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, IDatabaseConnection db)
{
if (itemId.Equals(Guid.Empty))
{
@@ -5723,7 +5702,7 @@ AND Type = @InternalPersonType)");
InsertItemValues(guidBlob, values, db);
}
- private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
+ private void InsertItemValues(byte[] idBlob, List<(int MagicNumber, string Value)> values, IDatabaseConnection db)
{
const int Limit = 100;
var startIndex = 0;
@@ -5755,7 +5734,7 @@ AND Type = @InternalPersonType)");
var currentValueInfo = values[i];
- var itemValue = currentValueInfo.Item2;
+ var itemValue = currentValueInfo.Value;
// Don't save if invalid
if (string.IsNullOrWhiteSpace(itemValue))
@@ -5763,7 +5742,7 @@ AND Type = @InternalPersonType)");
continue;
}
- statement.TryBind("@Type" + index, currentValueInfo.Item1);
+ statement.TryBind("@Type" + index, currentValueInfo.MagicNumber);
statement.TryBind("@Value" + index, itemValue);
statement.TryBind("@CleanValue" + index, GetCleanValue(itemValue));
}
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index 4a5f72327..d43996c69 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -464,6 +464,7 @@ namespace Emby.Server.Implementations.EntryPoints
public void Dispose()
{
Dispose(true);
+ GC.SuppressFinalize(this);
}
/// <summary>
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index b3bd3421a..b87f1bc22 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -42,17 +42,14 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="logger">The logger.</param>
/// <param name="socket">The socket.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
- /// <param name="query">The query.</param>
public WebSocketConnection(
ILogger<WebSocketConnection> logger,
WebSocket socket,
- IPAddress? remoteEndPoint,
- IQueryCollection query)
+ IPAddress? remoteEndPoint)
{
_logger = logger;
_socket = socket;
RemoteEndPoint = remoteEndPoint;
- QueryString = query;
_jsonOptions = JsonDefaults.Options;
LastActivityDate = DateTime.Now;
@@ -82,12 +79,6 @@ namespace Emby.Server.Implementations.HttpServer
public DateTime LastKeepAliveDate { get; set; }
/// <summary>
- /// Gets the query string.
- /// </summary>
- /// <value>The query string.</value>
- public IQueryCollection QueryString { get; }
-
- /// <summary>
/// Gets the state.
/// </summary>
/// <value>The state.</value>
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
index e99876dce..4f7d1c40a 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -50,8 +51,7 @@ namespace Emby.Server.Implementations.HttpServer
using var connection = new WebSocketConnection(
_loggerFactory.CreateLogger<WebSocketConnection>(),
webSocket,
- context.Connection.RemoteIpAddress,
- context.Request.Query)
+ context.GetNormalizedRemoteIp())
{
OnReceive = ProcessWebSocketMessageReceived
};
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.HttpServer
var tasks = new Task[_webSocketListeners.Length];
for (var i = 0; i < _webSocketListeners.Length; ++i)
{
- tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection);
+ tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context);
}
await Task.WhenAll(tasks).ConfigureAwait(false);
diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs
index e4f5f4cf0..f55c16d6d 100644
--- a/Emby.Server.Implementations/IO/StreamHelper.cs
+++ b/Emby.Server.Implementations/IO/StreamHelper.cs
@@ -17,11 +17,11 @@ namespace Emby.Server.Implementations.IO
try
{
int read;
- while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
cancellationToken.ThrowIfCancellationRequested();
- await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
if (onStarted != null)
{
@@ -44,11 +44,11 @@ namespace Emby.Server.Implementations.IO
if (emptyReadLimit <= 0)
{
int read;
- while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
cancellationToken.ThrowIfCancellationRequested();
- await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
}
return;
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO
{
cancellationToken.ThrowIfCancellationRequested();
- var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ var bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
if (bytesRead == 0)
{
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.IO
{
eofCount = 0;
- await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
}
}
}
@@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.IO
{
int bytesRead;
- while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
var bytesToWrite = Math.Min(bytesRead, copyLength);
if (bytesToWrite > 0)
{
- await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false);
}
copyLength -= bytesToWrite;
@@ -137,9 +137,9 @@ namespace Emby.Server.Implementations.IO
int bytesRead;
int totalBytesRead = 0;
- while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
- await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
}
diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
index 7e12ebb08..7958eb8f5 100644
--- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
@@ -68,9 +68,9 @@ namespace Emby.Server.Implementations.Images
DtoOptions = new DtoOptions(false),
ImageTypes = new ImageType[] { ImageType.Primary },
Limit = 8,
- OrderBy = new ValueTuple<string, SortOrder>[]
+ OrderBy = new[]
{
- new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
+ (ItemSortBy.Random, SortOrder.Ascending)
},
IncludeItemTypes = includeItemTypes
});
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index e30fa7097..bd0c178fd 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -13,7 +13,7 @@ using System.Threading;
using System.Threading.Tasks;
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.Tasks;
@@ -78,6 +78,7 @@ namespace Emby.Server.Implementations.Library
private readonly IItemRepository _itemRepository;
private readonly IImageProcessor _imageProcessor;
private readonly NamingOptions _namingOptions;
+ private readonly ExtraResolver _extraResolver;
/// <summary>
/// The _root folder sync lock.
@@ -146,6 +147,8 @@ namespace Emby.Server.Implementations.Library
_memoryCache = memoryCache;
_namingOptions = namingOptions;
+ _extraResolver = new ExtraResolver(namingOptions);
+
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
RecordConfigurationValues(configurationManager.Configuration);
@@ -1373,7 +1376,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetItemIdsList(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1384,7 +1387,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetStudios(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1395,7 +1398,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetGenres(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1406,7 +1409,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetMusicGenres(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1417,7 +1420,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetAllArtists(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1441,7 +1444,7 @@ namespace Emby.Server.Implementations.Library
for (int i = 0; i < len; i++)
{
parents[i] = GetItemById(ancestorIds[i]);
- if (!(parents[i] is ICollectionFolder || parents[i] is UserView))
+ if (parents[i] is not (ICollectionFolder or UserView))
{
return;
}
@@ -1458,7 +1461,7 @@ namespace Emby.Server.Implementations.Library
}
}
- public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1757,7 +1760,7 @@ namespace Emby.Server.Implementations.Library
return orderedItems ?? items;
}
- public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy)
+ public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy)
{
var isFirst = true;
@@ -2692,95 +2695,55 @@ namespace Emby.Server.Implementations.Library
}
var count = fileSystemChildren.Count;
- var files = new List<VideoFileInfo>();
- var nonVideoFiles = new List<FileSystemMetadata>();
for (var i = 0; i < count; i++)
{
var current = fileSystemChildren[i];
if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
{
- var filesInSubFolder = _fileSystem.GetFiles(current.FullName, _namingOptions.VideoFileExtensions, false, false);
+ var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false);
foreach (var file in filesInSubFolder)
{
- var videoInfo = VideoResolver.Resolve(file.FullName, file.IsDirectory, _namingOptions);
- if (videoInfo == null)
+ if (!_extraResolver.TryGetExtraTypeForOwner(file.FullName, ownerVideoInfo, out var extraType))
{
- nonVideoFiles.Add(file);
continue;
}
- files.Add(videoInfo);
+ var extra = GetExtra(file, extraType.Value);
+ if (extra != null)
+ {
+ yield return extra;
+ }
}
}
- else if (!current.IsDirectory)
+ else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType))
{
- var videoInfo = VideoResolver.Resolve(current.FullName, current.IsDirectory, _namingOptions);
- if (videoInfo == null)
+ var extra = GetExtra(current, extraType.Value);
+ if (extra != null)
{
- nonVideoFiles.Add(current);
- continue;
+ yield return extra;
}
-
- files.Add(videoInfo);
}
}
- if (files.Count == 0)
+ BaseItem GetExtra(FileSystemMetadata file, ExtraType extraType)
{
- 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), null, directoryService);
- if (resolved is not Video video)
+ var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType));
+ if (extra is not Video && extra is not Audio)
{
- continue;
- }
-
- // 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.ExtraType = currentExtra.ExtraType;
- video.ParentId = Guid.Empty;
- 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;
+ return null;
}
// Try to retrieve it from the db. If we don't find it, use the resolved version
- if (GetItemById(themeSong.Id) is Audio dbItem)
+ var itemById = GetItemById(extra.Id);
+ if (itemById != null)
{
- themeSong = dbItem;
+ extra = itemById;
}
- themeSong.ExtraType = ExtraType.ThemeSong;
- themeSong.OwnerId = owner.Id;
- themeSong.ParentId = Guid.Empty;
-
- yield return themeSong;
+ extra.ExtraType = extraType;
+ extra.ParentId = Guid.Empty;
+ extra.OwnerId = owner.Id;
+ return extra;
}
}
@@ -3044,7 +3007,10 @@ namespace Emby.Server.Implementations.Library
}
}
- CreateItems(personsToSave, null, CancellationToken.None);
+ if (personsToSave.Count > 0)
+ {
+ CreateItems(personsToSave, null, CancellationToken.None);
+ }
}
private void StartScanInBackground()
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 972d4ebbb..a414e7e16 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -464,12 +464,11 @@ namespace Emby.Server.Implementations.Library
try
{
- var tuple = GetProvider(request.OpenToken);
- var provider = tuple.Item1;
+ var (provider, keyId) = GetProvider(request.OpenToken);
var currentLiveStreams = _openStreams.Values.ToList();
- liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false);
+ liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
mediaSource = liveStream.MediaSource;
@@ -829,7 +828,7 @@ namespace Emby.Server.Implementations.Library
}
}
- private (IMediaSourceProvider, string) GetProvider(string key)
+ private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
{
if (string.IsNullOrEmpty(key))
{
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 6f61dc713..64e7d5446 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Library
var attributeEnd = attributeIndex + attribute.Length;
if (attributeIndex > 0
&& str[attributeIndex - 1] == '['
- && str[attributeEnd] == '=')
+ && (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
{
var closingIndex = str[attributeEnd..].IndexOf(']');
// Must be at least 1 character before the closing bracket.
diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
new file mode 100644
index 000000000..807913b5d
--- /dev/null
+++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Model.Entities;
+using static Emby.Naming.Video.ExtraRuleResolver;
+
+namespace Emby.Server.Implementations.Library.Resolvers
+{
+ /// <summary>
+ /// Resolves a Path into a Video or Video subclass.
+ /// </summary>
+ internal class ExtraResolver
+ {
+ private readonly NamingOptions _namingOptions;
+ private readonly IItemResolver[] _trailerResolvers;
+ private readonly IItemResolver[] _videoResolvers;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ExtraResolver"/> class.
+ /// </summary>
+ /// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param>
+ public ExtraResolver(NamingOptions namingOptions)
+ {
+ _namingOptions = namingOptions;
+ _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(namingOptions) };
+ _videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(namingOptions) };
+ }
+
+ /// <summary>
+ /// Gets the resolvers for the extra type.
+ /// </summary>
+ /// <param name="extraType">The extra type.</param>
+ /// <returns>The resolvers for the extra type.</returns>
+ public IItemResolver[]? GetResolversForExtraType(ExtraType extraType) => extraType switch
+ {
+ ExtraType.Trailer => _trailerResolvers,
+ // For audio we'll have to rely on the AudioResolver, which is a "built-in"
+ ExtraType.ThemeSong => null,
+ _ => _videoResolvers
+ };
+
+ public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out ExtraType? extraType)
+ {
+ var extraResult = GetExtraInfo(path, _namingOptions);
+ if (extraResult.ExtraType == null)
+ {
+ extraType = null;
+ return false;
+ }
+
+ var cleanDateTimeResult = CleanDateTimeParser.Clean(Path.GetFileNameWithoutExtension(path), _namingOptions.CleanDateTimeRegexes);
+ var name = cleanDateTimeResult.Name;
+ var year = cleanDateTimeResult.Year;
+
+ var parentDir = ownerVideoFileInfo.IsDirectory ? ownerVideoFileInfo.Path : Path.GetDirectoryName(ownerVideoFileInfo.Path.AsSpan());
+
+ var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(ownerVideoFileInfo.FileNameWithoutExtension, _namingOptions.VideoFlagDelimiters);
+ var trimmedVideoInfoName = TrimFilenameDelimiters(ownerVideoFileInfo.Name, _namingOptions.VideoFlagDelimiters);
+ var trimmedExtraFileName = TrimFilenameDelimiters(name, _namingOptions.VideoFlagDelimiters);
+
+ // first check filenames
+ bool isValid = StartsWith(trimmedExtraFileName, trimmedFileNameWithoutExtension)
+ || (StartsWith(trimmedExtraFileName, trimmedVideoInfoName) && year == ownerVideoFileInfo.Year);
+
+ if (!isValid)
+ {
+ // When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
+ var currentParentDir = extraResult.Rule?.RuleType == ExtraRuleType.DirectoryName
+ ? Path.GetDirectoryName(Path.GetDirectoryName(path.AsSpan()))
+ : Path.GetDirectoryName(path.AsSpan());
+
+ isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase);
+ }
+
+ extraType = extraResult.ExtraType;
+ return isValid;
+ }
+
+ 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.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
new file mode 100644
index 000000000..b8554bd51
--- /dev/null
+++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
@@ -0,0 +1,24 @@
+#nullable disable
+
+using Emby.Naming.Common;
+using MediaBrowser.Controller.Entities;
+
+namespace Emby.Server.Implementations.Library.Resolvers
+{
+ /// <summary>
+ /// Resolves a Path into an instance of the <see cref="Video"/> class.
+ /// </summary>
+ /// <typeparam name="T">The type of item to resolve.</typeparam>
+ public class GenericVideoResolver<T> : BaseVideoResolver<T>
+ where T : Video, new()
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class.
+ /// </summary>
+ /// <param name="namingOptions">The naming options.</param>
+ public GenericVideoResolver(NamingOptions namingOptions)
+ : base(namingOptions)
+ {
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoExtraResolver.cs
deleted file mode 100644
index 9aadce88c..000000000
--- a/Emby.Server.Implementations/Library/Resolvers/VideoExtraResolver.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-#nullable disable
-
-using Emby.Naming.Common;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Model.Entities;
-
-namespace Emby.Server.Implementations.Library.Resolvers
-{
- /// <summary>
- /// Resolves a Path into a Video or Video subclass.
- /// </summary>
- public class VideoExtraResolver : BaseVideoResolver<Video>
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="VideoExtraResolver"/> class.
- /// </summary>
- /// <param name="namingOptions">The naming options.</param>
- public VideoExtraResolver(NamingOptions namingOptions)
- : base(namingOptions)
- {
- }
-
- /// <summary>
- /// Gets the priority.
- /// </summary>
- /// <value>The priority.</value>
- public override ResolverPriority Priority => ResolverPriority.Last;
-
- /// <summary>
- /// Resolves the specified args.
- /// </summary>
- /// <param name="args">The args.</param>
- /// <returns>The video extra or null if not handled by this resolver.</returns>
- public override Video Resolve(ItemResolveArgs args)
- {
- // Only handle owned items
- if (args.Parent != null)
- {
- return null;
- }
-
- var ownedItem = base.Resolve(args);
-
- // Re-resolve items that have their own type
- if (ownedItem.ExtraType == ExtraType.Trailer)
- {
- ownedItem = ResolveVideo<Trailer>(args, false);
- }
-
- return ownedItem;
- }
- }
-}
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 4aacf7774..55911933a 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -190,7 +190,7 @@ namespace Emby.Server.Implementations.Library
searchQuery.ParentId = Guid.Empty;
searchQuery.IncludeItemsByName = true;
searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
- mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList();
+ mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList();
}
else
{
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 2fbd47bc7..a8440102d 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -643,7 +643,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CancellationToken cancellationToken)
{
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
+#pragma warning disable CA5350 // SchedulesDirect is always SHA1.
var hashedPasswordBytes = SHA1.HashData(Encoding.ASCII.GetBytes(password));
+#pragma warning restore CA5350
// TODO: remove ToLower when Convert.ToHexString supports lowercase
// Schedules Direct requires the hex to be lowercase
string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant();
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 047d8e98c..aa3598c8b 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -207,7 +207,7 @@ namespace Emby.Server.Implementations.LiveTv
orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
}
- if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
+ if (!internalQuery.OrderBy.Any(i => string.Equals(i.OrderBy, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
{
orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending));
}
@@ -520,7 +520,7 @@ namespace Emby.Server.Implementations.LiveTv
return item;
}
- private (LiveTvProgram item, bool isNew, bool isUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
+ private (LiveTvProgram Item, bool IsNew, bool IsUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
{
var id = _tvDtoService.GetInternalProgramId(info.Id);
@@ -779,9 +779,9 @@ namespace Emby.Server.Implementations.LiveTv
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
- var list = new List<Tuple<BaseItemDto, string, string>>
+ var list = new List<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)>
{
- new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId)
+ (dto, program.ExternalId, program.ExternalSeriesId)
};
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
@@ -976,16 +976,16 @@ namespace Emby.Server.Implementations.LiveTv
return score;
}
- private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string>> programs, CancellationToken cancellationToken)
+ private async Task AddRecordingInfo(IEnumerable<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)> programs, CancellationToken cancellationToken)
{
IReadOnlyList<TimerInfo> timerList = null;
IReadOnlyList<SeriesTimerInfo> seriesTimerList = null;
foreach (var programTuple in programs)
{
- var program = programTuple.Item1;
- var externalProgramId = programTuple.Item2;
- string externalSeriesId = programTuple.Item3;
+ var program = programTuple.ItemDto;
+ var externalProgramId = programTuple.ExternalId;
+ string externalSeriesId = programTuple.ExternalSeriesId;
timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
@@ -1186,13 +1186,13 @@ namespace Emby.Server.Implementations.LiveTv
foreach (var program in channelPrograms)
{
var programTuple = GetProgram(program, existingPrograms, currentChannel);
- var programItem = programTuple.item;
+ var programItem = programTuple.Item;
- if (programTuple.isNew)
+ if (programTuple.IsNew)
{
newPrograms.Add(programItem);
}
- else if (programTuple.isUpdated)
+ else if (programTuple.IsUpdated)
{
updatedPrograms.Add(programItem);
}
@@ -1423,9 +1423,9 @@ namespace Emby.Server.Implementations.LiveTv
return result;
}
- public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
+ public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
{
- var programTuples = new List<Tuple<BaseItemDto, string, string>>();
+ var programTuples = new List<(BaseItemDto Dto, string ExternalId, string ExternalSeriesId)>();
var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
@@ -1461,7 +1461,7 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- programTuples.Add(new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId));
+ programTuples.Add((dto, program.ExternalId, program.ExternalSeriesId));
}
return AddRecordingInfo(programTuples, CancellationToken.None);
@@ -1868,11 +1868,11 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetItemById(internalChannelId);
}
- public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user)
+ public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user)
{
var now = DateTime.UtcNow;
- var channelIds = items.Select(i => i.Item2.Id).Distinct().ToArray();
+ var channelIds = items.Select(i => i.Channel.Id).Distinct().ToArray();
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
{
@@ -1893,11 +1893,8 @@ namespace Emby.Server.Implementations.LiveTv
var addCurrentProgram = options.AddCurrentProgram;
- foreach (var tuple in items)
+ foreach (var (dto, channel) in items)
{
- var dto = tuple.Item1;
- var channel = tuple.Item2;
-
dto.Number = channel.Number;
dto.ChannelNumber = channel.Number;
dto.ChannelType = channel.ChannelType;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
index 069b4fab6..aae33503f 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_profile = profile;
}
- public IEnumerable<(string, string)> GetCommands()
+ public IEnumerable<(string CommandName, string CommandValue)> GetCommands()
{
if (!string.IsNullOrEmpty(_channel))
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index f009c77cf..48d9e316d 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_remoteEndPoint = new IPEndPoint(remoteIp, HdHomeRunPort);
_tcpClient = new TcpClient();
- _tcpClient.Connect(_remoteEndPoint);
+ await _tcpClient.ConnectAsync(_remoteEndPoint, cancellationToken).ConfigureAwait(false);
if (!_lockkey.HasValue)
{
@@ -114,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
foreach (var command in commands.GetCommands())
{
- var channelMsgLen = WriteSetMessage(buffer, i, command.Item1, command.Item2, lockKeyValue);
+ var channelMsgLen = WriteSetMessage(buffer, i, command.CommandName, command.CommandValue, lockKeyValue);
await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
@@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
using var tcpClient = new TcpClient();
- tcpClient.Connect(_remoteEndPoint);
+ await tcpClient.ConnectAsync(_remoteEndPoint, cancellationToken).ConfigureAwait(false);
using var stream = tcpClient.GetStream();
var commandList = commands.GetCommands();
@@ -167,7 +167,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
foreach (var command in commandList)
{
- var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.Item1, command.Item2, _lockkey);
+ var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.CommandName, command.CommandValue, _lockkey);
await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index d2f033439..a5edd35cc 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
}
- catch (OperationCanceledException ex)
+ catch (Exception ex) when (ex is OperationCanceledException || ex is TimeoutException)
{
Logger.LogInformation("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
openTaskCompletionSource.TrySetException(ex);
@@ -191,36 +191,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
- using (var timeOutSource = new CancellationTokenSource())
- using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(
- cancellationToken,
- timeOutSource.Token))
+ var res = await udpClient.ReceiveAsync(cancellationToken)
+ .AsTask()
+ .WaitAsync(TimeSpan.FromMilliseconds(30000), CancellationToken.None)
+ .ConfigureAwait(false);
+ var buffer = res.Buffer;
+
+ var read = buffer.Length - RtpHeaderBytes;
+
+ if (read > 0)
+ {
+ await fileStream.WriteAsync(buffer.AsMemory(RtpHeaderBytes, read), cancellationToken).ConfigureAwait(false);
+ }
+
+ if (!resolved)
{
- var resTask = udpClient.ReceiveAsync(linkedSource.Token).AsTask();
- if (await Task.WhenAny(resTask, Task.Delay(30000, linkedSource.Token)).ConfigureAwait(false) != resTask)
- {
- resTask.Dispose();
- break;
- }
-
- // We don't want all these delay tasks to keep running
- timeOutSource.Cancel();
- var res = await resTask.ConfigureAwait(false);
- var buffer = res.Buffer;
-
- var read = buffer.Length - RtpHeaderBytes;
-
- if (read > 0)
- {
- fileStream.Write(buffer, RtpHeaderBytes, read);
- }
-
- if (!resolved)
- {
- resolved = true;
- DateOpened = DateTime.UtcNow;
- openTaskCompletionSource.TrySetResult(true);
- }
+ resolved = true;
+ DateOpened = DateTime.UtcNow;
+ openTaskCompletionSource.TrySetResult(true);
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
index 153354932..11bd40ab1 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
@@ -6,6 +6,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public interface IHdHomerunChannelCommands
{
- IEnumerable<(string, string)> GetCommands();
+ IEnumerable<(string CommandName, string CommandValue)> GetCommands();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
index 26627b8aa..80d9d0724 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
@@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- public IEnumerable<(string, string)> GetCommands()
+ public IEnumerable<(string CommandName, string CommandValue)> GetCommands()
{
if (!string.IsNullOrEmpty(_channel))
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index b1ce7b2b3..ab4beb15b 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -51,7 +49,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var url = mediaSource.Path;
- Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
+ Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath) ?? throw new InvalidOperationException("Path can't be a root directory."));
var typeName = GetType().Name;
Logger.LogInformation("Opening {StreamType} Live stream from {Url}", typeName, url);
@@ -94,14 +92,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
// OpenedMediaSource.SupportsDirectPlay = false;
// OpenedMediaSource.SupportsDirectStream = true;
// OpenedMediaSource.SupportsTranscoding = true;
- await taskCompletionSource.Task.ConfigureAwait(false);
- if (taskCompletionSource.Task.Exception != null)
- {
- // Error happened while opening the stream so raise the exception again to inform the caller
- throw taskCompletionSource.Task.Exception;
- }
-
- if (!taskCompletionSource.Task.Result)
+ var res = await taskCompletionSource.Task.ConfigureAwait(false);
+ if (!res)
{
Logger.LogWarning("Zero bytes copied from stream {StreamType} to {FilePath} but no exception raised", GetType().Name, TempFilePath);
throw new EndOfStreamException(string.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name));
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index 2dee5e327..9bab3b9a9 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -15,7 +15,7 @@
"Favorites": "Preferits",
"Folders": "Carpetes",
"Genres": "Gèneres",
- "HeaderAlbumArtists": "Àlbum de l'artista",
+ "HeaderAlbumArtists": "Artistes de l'àlbum",
"HeaderContinueWatching": "Continua Veient",
"HeaderFavoriteAlbums": "Àlbums Preferits",
"HeaderFavoriteArtists": "Artistes Predilectes",
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index c924e5c15..115f36e7c 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -71,7 +71,7 @@
"ScheduledTaskStartedWithName": "{0} wurde gestartet",
"ServerNameNeedsToBeRestarted": "{0} muss neu gestartet werden",
"Shows": "Serien",
- "Songs": "Songs",
+ "Songs": "Lieder",
"StartupEmbyServerIsLoading": "Jellyfin-Server startet, bitte versuche es gleich noch einmal.",
"SubtitleDownloadFailureForItem": "Download der Untertitel fehlgeschlagen für {0}",
"SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden",
@@ -92,25 +92,25 @@
"ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
"ValueSpecialEpisodeName": "Extra - {0}",
"VersionNumber": "Version {0}",
- "TaskDownloadMissingSubtitlesDescription": "Durchsucht das Internet nach fehlenden Untertiteln, basierend auf den Meta Einstellungen.",
+ "TaskDownloadMissingSubtitlesDescription": "Suche im Internet basierend auf den Metadaten-Einstellungen nach fehlenden Untertiteln.",
"TaskDownloadMissingSubtitles": "Lade fehlende Untertitel herunter",
- "TaskRefreshChannelsDescription": "Aktualisiere Internet Kanal Informationen.",
+ "TaskRefreshChannelsDescription": "Aktualisiere Internet-Kanal-Informationen.",
"TaskRefreshChannels": "Aktualisiere Kanäle",
- "TaskCleanTranscodeDescription": "Löscht Transkodierdateien, welche älter als einen Tag sind.",
- "TaskCleanTranscode": "Lösche Transkodier-Pfad",
+ "TaskCleanTranscodeDescription": "Löscht Transkodierdateien, die älter als einen Tag sind.",
+ "TaskCleanTranscode": "Räume Transkodierungs-Verzeichnis auf",
"TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche für automatische Updates konfiguriert sind und installiert diese.",
"TaskUpdatePlugins": "Aktualisiere Plugins",
"TaskRefreshPeopleDescription": "Aktualisiert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.",
- "TaskRefreshPeople": "Aktualisiere Schauspieler",
- "TaskCleanLogsDescription": "Lösche Log Dateien, die älter als {0} Tage sind.",
- "TaskCleanLogs": "Lösche Log-Verzeichnis",
- "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken nach neu hinzugefügten Dateien und aktualisiere Metadaten.",
+ "TaskRefreshPeople": "Aktualisiere Personen",
+ "TaskCleanLogsDescription": "Lösche Log-Dateien, die älter als {0} Tage sind.",
+ "TaskCleanLogs": "Räumt Log-Verzeichnis auf",
+ "TaskRefreshLibraryDescription": "Scannt alle Bibliotheken nach neu hinzugefügten Dateien und aktualisiere Metadaten.",
"TaskRefreshLibrary": "Scanne Medien-Bibliothek",
- "TaskRefreshChapterImagesDescription": "Erstellt Vorschaubilder für Videos, welche Kapitel besitzen.",
- "TaskRefreshChapterImages": "Extrahiert Kapitel-Bilder",
+ "TaskRefreshChapterImagesDescription": "Erstellt Vorschaubilder für Videos, die Kapitel besitzen.",
+ "TaskRefreshChapterImages": "Extrahiere Kapitel-Bilder",
"TaskCleanCacheDescription": "Löscht nicht mehr benötigte Zwischenspeicherdateien.",
"TaskCleanCache": "Leere Zwischenspeicher",
- "TasksChannelsCategory": "Internet Kanäle",
+ "TasksChannelsCategory": "Internet-Kanäle",
"TasksApplicationCategory": "Anwendung",
"TasksLibraryCategory": "Bibliothek",
"TasksMaintenanceCategory": "Wartung",
diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json
index 697063f26..9952c05ca 100644
--- a/Emby.Server.Implementations/Localization/Core/el.json
+++ b/Emby.Server.Implementations/Localization/Core/el.json
@@ -15,7 +15,7 @@
"Favorites": "Αγαπημένα",
"Folders": "Φάκελοι",
"Genres": "Είδη",
- "HeaderAlbumArtists": "Άλμπουμ Καλλιτέχνη",
+ "HeaderAlbumArtists": "Καλλιτέχνες άλμπουμ",
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json
index 3364ee333..5bfe8c0b1 100644
--- a/Emby.Server.Implementations/Localization/Core/gsw.json
+++ b/Emby.Server.Implementations/Localization/Core/gsw.json
@@ -118,5 +118,6 @@
"TaskCleanActivityLog": "Aktivitätsprotokoll aufräumen",
"Undefined": "Undefiniert",
"Forced": "Erzwungen",
- "Default": "Standard"
+ "Default": "Standard",
+ "TaskOptimizeDatabase": "Datenbank optimieren"
}
diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json
index 981e8a06e..e32ab4ca8 100644
--- a/Emby.Server.Implementations/Localization/Core/he.json
+++ b/Emby.Server.Implementations/Localization/Core/he.json
@@ -118,5 +118,7 @@
"TaskCleanActivityLog": "נקה רשומת פעילות",
"Undefined": "לא מוגדר",
"Forced": "כפוי",
- "Default": "ברירת מחדל"
+ "Default": "ברירת מחדל",
+ "TaskOptimizeDatabase": "מיטוב מסד נתונים",
+ "TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים."
}
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index a37de0748..50d019f90 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -15,7 +15,7 @@
"Favorites": "즐겨찾기",
"Folders": "폴더",
"Genres": "장르",
- "HeaderAlbumArtists": "아티스트의 앨범",
+ "HeaderAlbumArtists": "앨범 음악가",
"HeaderContinueWatching": "계속 시청하기",
"HeaderFavoriteAlbums": "즐겨찾는 앨범",
"HeaderFavoriteArtists": "즐겨찾는 아티스트",
diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json
index f0a07f604..881cd4a93 100644
--- a/Emby.Server.Implementations/Localization/Core/lt-LT.json
+++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json
@@ -118,5 +118,6 @@
"Undefined": "Neapibrėžtas",
"Forced": "Priverstas",
"Default": "Numatytas",
- "TaskCleanActivityLogDescription": "Ištrina veiklos žuranlo įrašus, kurie yra senesni nei nustatytas amžius."
+ "TaskCleanActivityLogDescription": "Ištrina veiklos žuranlo įrašus, kurie yra senesni nei nustatytas amžius.",
+ "TaskOptimizeDatabase": "Optimizuoti duomenų bazės"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ml.json b/Emby.Server.Implementations/Localization/Core/ml.json
index 09ef34913..acc7746c1 100644
--- a/Emby.Server.Implementations/Localization/Core/ml.json
+++ b/Emby.Server.Implementations/Localization/Core/ml.json
@@ -6,7 +6,7 @@
"ChapterNameValue": "അധ്യായം {0}",
"DeviceOfflineWithName": "{0} വിച്ഛേദിച്ചു",
"DeviceOnlineWithName": "{0} ബന്ധിപ്പിച്ചു",
- "FailedLoginAttemptWithUserName": "Log 0 from എന്നതിൽ നിന്നുള്ള പ്രവേശന ശ്രമം പരാജയപ്പെട്ടു",
+ "FailedLoginAttemptWithUserName": "{0} - എന്നതിൽ നിന്നുള്ള പ്രവേശന ശ്രമം പരാജയപ്പെട്ടു",
"Forced": "നിർബന്ധിച്ചു",
"HeaderFavoriteAlbums": "പ്രിയപ്പെട്ട ആൽബങ്ങൾ",
"HeaderFavoriteArtists": "പ്രിയപ്പെട്ട കലാകാരന്മാർ",
diff --git a/Emby.Server.Implementations/Localization/Core/my.json b/Emby.Server.Implementations/Localization/Core/my.json
new file mode 100644
index 000000000..418376c4e
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/my.json
@@ -0,0 +1,123 @@
+{
+ "Default": "ပုံသေ",
+ "Collections": "စုစည်းမှုများ",
+ "Channels": "ချန်နယ်များ",
+ "Books": "စာအုပ်များ",
+ "Artists": "အနုပညာရှင်များ",
+ "Albums": "အခွေများ",
+ "TaskOptimizeDatabaseDescription": "ဒေတာဘေ့စ်ကို ကျစ်လစ်စေပြီး နေရာလွတ်များကို ဖြတ်တောက်ပေးသည်။ စာကြည့်တိုက်ကို စကင်န်ဖတ်ပြီးနောက် ဤလုပ်ငန်းကို လုပ်ဆောင်ခြင်း သို့မဟုတ် ဒေတာဘေ့စ်မွမ်းမံမှုများ စွမ်းဆောင်ရည်ကို မြှင့်တင်ပေးနိုင်သည်ဟု ရည်ညွှန်းသော အခြားပြောင်းလဲမှုများကို လုပ်ဆောင်ခြင်း။.",
+ "TaskOptimizeDatabase": "ဒေတာဘေ့စ်ကို အကောင်းဆုံးဖြစ်အောင်လုပ်ပါ။",
+ "TaskDownloadMissingSubtitlesDescription": "မက်တာဒေတာ ဖွဲ့စည်းမှုပုံစံအပေါ် အခြေခံ၍ ပျောက်ဆုံးနေသော စာတန်းထိုးများအတွက် အင်တာနက်ကို ရှာဖွေသည်။",
+ "TaskDownloadMissingSubtitles": "ပျောက်ဆုံးနေသော စာတန်းထိုးများကို ဒေါင်းလုဒ်လုပ်ပါ။",
+ "TaskRefreshChannelsDescription": "အင်တာနက်ချန်နယ်အချက်အလက်ကို ပြန်လည်စတင်သည်။",
+ "TaskRefreshChannels": "ချန်နယ်များကို ပြန်လည်စတင်ပါ။",
+ "TaskCleanTranscodeDescription": "သက်တမ်း တစ်ရက်ထက်ပိုသော အသွင်ပြောင်းကုဒ်ဖိုင်များကို ဖျက်ပါ။",
+ "TaskCleanTranscode": "Transcode လမ်းညွှန်ကို သန့်ရှင်းပါ။",
+ "TaskUpdatePluginsDescription": "အလိုအလျောက် အပ်ဒိတ်လုပ်ရန် စီစဉ်ထားသော ပလပ်အင်များအတွက် အပ်ဒိတ်များကို ဒေါင်းလုဒ်လုပ်ပြီး ထည့်သွင်းပါ။",
+ "TaskUpdatePlugins": "ပလပ်အင်များကို အပ်ဒိတ်လုပ်ပါ။",
+ "TaskRefreshPeopleDescription": "သင့်မီဒီယာစာကြည့်တိုက်ရှိ သရုပ်ဆောင်များနှင့် ဒါရိုက်တာများအတွက် မက်တာဒေတာကို အပ်ဒိတ်လုပ်ပါ။",
+ "TaskRefreshPeople": "လူများကို ပြန်လည်ဆန်းသစ်ပါ။",
+ "TaskCleanLogsDescription": "{0} ရက်ထက်ပိုသော မှတ်တမ်းဖိုင်များကို ဖျက်သည်။",
+ "TaskCleanLogs": "မှတ်တမ်းလမ်းညွှန်ကို သန့်ရှင်းပါ။",
+ "TaskRefreshLibraryDescription": "ဖိုင်အသစ်များအတွက် သင့်မီဒီယာဒစ်ဂျစ်တိုက်ကို စကင်န်ဖတ်ပြီး မက်တာဒေတာကို ပြန်လည်စတင်ပါ။",
+ "TaskRefreshLibrary": "မီဒီယာစာကြည့်တိုက်ကို စကင်န်ဖတ်ပါ။",
+ "TaskRefreshChapterImagesDescription": "အခန်းများပါရှိသော ဗီဒီယိုများအတွက် ပုံသေးများကို ဖန်တီးပါ။",
+ "TaskRefreshChapterImages": "အခန်းပုံများကို ထုတ်ယူပါ။",
+ "TaskCleanCacheDescription": "စနစ်မှ မလိုအပ်တော့သော ကက်ရှ်ဖိုင်များကို ဖျက်ပါ။.",
+ "TaskCleanCache": "Cache Directory ကို ရှင်းပါ။",
+ "TaskCleanActivityLogDescription": "စီစဉ်သတ်မှတ်ထားသော အသက်ထက် ပိုကြီးသော လုပ်ဆောင်ချက်မှတ်တမ်းများကို ဖျက်ပါ။",
+ "TaskCleanActivityLog": "လုပ်ဆောင်ချက်မှတ်တမ်းကို ရှင်းလင်းပါ။",
+ "TasksChannelsCategory": "အင်တာနက်ချန်နယ်များ",
+ "TasksApplicationCategory": "အပလီကေးရှင်း",
+ "TasksLibraryCategory": "စာကြည့်တိုက်",
+ "TasksMaintenanceCategory": "ထိန်းသိမ်းခြင်း",
+ "VersionNumber": "ဗားရှင်း {0}",
+ "ValueSpecialEpisodeName": "အထူး- {0}",
+ "ValueHasBeenAddedToLibrary": "{0} ကို သင့်မီဒီယာဒစ်ဂျစ်တိုက်သို့ ပေါင်းထည့်လိုက်ပါပြီ။",
+ "UserStoppedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ဖွင့်ပြီးပါပြီ",
+ "UserStartedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ပြသနေသည်",
+ "UserPolicyUpdatedWithName": "{0} အတွက် အသုံးပြုသူမူဝါဒကို အပ်ဒိတ်လုပ်ပြီးပါပြီ",
+ "UserPasswordChangedWithName": "အသုံးပြုသူ {0} အတွက် စကားဝှက်ကို ပြောင်းထားသည်",
+ "UserOnlineFromDevice": "{0} သည် {1} မှ အွန်လိုင်းဖြစ်သည်",
+ "UserOfflineFromDevice": "{0} သည် {1} မှ ချိတ်ဆက်မှုပြတ်တောက်သွားသည်",
+ "UserLockedOutWithName": "အသုံးပြုသူ {0} အား လော့ခ်ချထားသည်။",
+ "UserDownloadingItemWithValues": "{0} သည် {1} ကို ဒေါင်းလုဒ်လုပ်နေသည်",
+ "UserDeletedWithName": "အသုံးပြုသူ {0} ကို ဖျက်လိုက်ပါပြီ။",
+ "UserCreatedWithName": "အသုံးပြုသူ {0} ကို ဖန်တီးပြီးပါပြီ။",
+ "User": "အသုံးပြုသူ",
+ "Undefined": "သတ်မှတ်မထားသော",
+ "TvShows": "တီဗီရှိုးများ",
+ "System": "စနစ်",
+ "Sync": "ထပ်တူကျသည်။",
+ "SubtitleDownloadFailureFromForItem": "စာတန်းထိုးများကို {1} အတွက် {0} မှ ဒေါင်းလုဒ်လုပ်၍ မရပါ",
+ "StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို ဖွင့်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။",
+ "Songs": "သီချင်းများ",
+ "Shows": "ရှိုးပွဲ",
+ "ServerNameNeedsToBeRestarted": "{0} ကို ပြန်လည်စတင်ရန် လိုအပ်သည်။",
+ "ScheduledTaskStartedWithName": "{0} စတင်ခဲ့သည်။",
+ "ScheduledTaskFailedWithName": "{0} မအောင်မြင်ပါ။",
+ "ProviderValue": "ဝန်ဆောင်မှုပေးသူ- {0}",
+ "PluginUpdatedWithName": "{0} ကို အပ်ဒိတ်လုပ်ထားသည်။",
+ "PluginUninstalledWithName": "{0} ကို ဖြုတ်လိုက်ပါပြီ။",
+ "PluginInstalledWithName": "{0} ကို ထည့်သွင်းခဲ့သည်။",
+ "Plugin": "ပလပ်အင်",
+ "Playlists": "အစီအစဉ်များ",
+ "Photos": "ဓာတ်ပုံများ",
+ "NotificationOptionVideoPlaybackStopped": "ဗီဒီယိုပြန်ဖွင့်ခြင်းကို ရပ်သွားသည်။",
+ "NotificationOptionVideoPlayback": "ဗီဒီယိုဖွင့်ခြင်း စတင်ပါပြီ။",
+ "NotificationOptionUserLockedOut": "အသုံးပြုသူ ထွက်သွားသည်။",
+ "NotificationOptionTaskFailed": "စီစဉ်ထားသော အလုပ်ပျက်ကွက်",
+ "NotificationOptionServerRestartRequired": "ဆာဗာ ပြန်လည်စတင်ရန် လိုအပ်သည်။",
+ "NotificationOptionPluginUpdateInstalled": "ပလပ်အင် အပ်ဒိတ် ထည့်သွင်းပြီးပါပြီ။",
+ "NotificationOptionPluginUninstalled": "ပလပ်အင်ကို ဖြုတ်လိုက်ပါပြီ။",
+ "NotificationOptionPluginInstalled": "ပလပ်အင် ထည့်သွင်းထားသည်။",
+ "NotificationOptionPluginError": "ပလပ်အင် ချို့ယွင်းခြင်း။",
+ "NotificationOptionNewLibraryContent": "အကြောင်းအရာအသစ် ထပ်ထည့်ထားပါတယ်။",
+ "NotificationOptionInstallationFailed": "တပ်ဆင်မှု မအောင်မြင်ပါ။",
+ "NotificationOptionCameraImageUploaded": "ကင်မရာပုံ အပ်လုဒ်လုပ်ထားသည်။",
+ "NotificationOptionAudioPlaybackStopped": "အသံပြန်ဖွင့်ခြင်းကို ရပ်သွားသည်။",
+ "NotificationOptionAudioPlayback": "အသံပြန်ဖွင့်ခြင်း စတင်ပါပြီ။",
+ "NotificationOptionApplicationUpdateInstalled": "အပလီကေးရှင်း အပ်ဒိတ်ကို ထည့်သွင်းထားသည်။",
+ "NotificationOptionApplicationUpdateAvailable": "အပလီကေးရှင်း အပ်ဒိတ် ရနိုင်ပါပြီ။",
+ "NewVersionIsAvailable": "Jellyfin Server ၏ ဗားရှင်းအသစ်ကို ဒေါင်းလုဒ်လုပ်နိုင်ပါသည်။",
+ "NameSeasonUnknown": "အမည််မသိ ဇာတ်လမ်းတွဲ",
+ "NameSeasonNumber": "ဇာတ်လမ်းတွဲ {0}",
+ "NameInstallFailed": "{0} ထည့်သွင်းမှု မအောင်မြင်ပါ။",
+ "MusicVideos": "ဂီတဗီဒီယိုများ",
+ "Music": "တေးဂီတ",
+ "Movies": "ရုပ်ရှင်များ",
+ "MixedContent": "ရောနှောပါဝင်မှု",
+ "MessageServerConfigurationUpdated": "ဆာဗာဖွဲ့စည်းပုံကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။",
+ "MessageNamedServerConfigurationUpdatedWithValue": "ဆာဗာဖွဲ့စည်းပုံကဏ္ဍ {0} ကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။",
+ "MessageApplicationUpdatedTo": "Jellyfin ဆာဗာကို {0} သို့ အပ်ဒိတ်လုပ်ထားသည်",
+ "MessageApplicationUpdated": "Jellyfin ဆာဗာကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။",
+ "Latest": "နောက်ဆုံး",
+ "LabelRunningTimeValue": "လည်ပတ်ချိန်- {0}",
+ "LabelIpAddressValue": "IP လိပ်စာ- {0}",
+ "ItemRemovedWithName": "{0} ကို ဒစ်ဂျစ်တိုက်မှ ဖယ်ရှားခဲ့သည်။",
+ "ItemAddedWithName": "{0} ကို စာကြည့်တိုက်သို့ ထည့်ထားသည်။",
+ "Inherit": "ဆက်လက် လုပ်ဆောင်သည်။",
+ "HomeVideos": "ပင်မဗီဒီယိုများ",
+ "HeaderRecordingGroups": "အသံဖမ်းအဖွဲ့များ",
+ "HeaderNextUp": "နောက်ထပ်",
+ "HeaderLiveTV": "Live TV",
+ "HeaderFavoriteSongs": "အကြိုက်ဆုံးသီချင်းများ",
+ "HeaderFavoriteShows": "အကြိုက်ဆုံးရှိုးများ",
+ "HeaderFavoriteEpisodes": "အကြိုက်ဆုံးအပိုင်းများ",
+ "HeaderFavoriteArtists": "အကြိုက်ဆုံးအနုပညာရှင်များ",
+ "HeaderFavoriteAlbums": "အကြိုက်ဆုံး အယ်လ်ဘမ်များ",
+ "HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ။",
+ "HeaderAlbumArtists": "အယ်လ်ဘမ်အနုပညာရှင်များ",
+ "Genres": "အမျိုးအစားများ",
+ "Forced": "အတင်းအကြပ်",
+ "Folders": "ဖိုဒါများ",
+ "Favorites": "အကြိုက်ဆုံးများ",
+ "FailedLoginAttemptWithUserName": "{0} မှ အကောင့်ဝင်ရန် မအောင်မြင်ပါ",
+ "DeviceOnlineWithName": "{0} ကို ချိတ်ဆက်ထားသည်။",
+ "DeviceOfflineWithName": "{0} နှင့် အဆက်ပြတ်သွားပါပြီ။",
+ "ChapterNameValue": "အခန်း {0}",
+ "CameraImageUploadedFrom": "ကင်မရာပုံအသစ်ကို {0} မှ အပ်လုဒ်လုပ်ထားသည်",
+ "AuthenticationSucceededWithUserName": "{0} စစ်မှန်ကြောင်း အောင်မြင်စွာ အတည်ပြုပြီးပါပြီ။",
+ "Application": "အပလီကေးရှင်း",
+ "AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index 2d7163275..dc3793f1b 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -96,7 +96,7 @@
"TaskRefreshChannels": "Обновление каналов",
"TaskCleanTranscode": "Очистка каталога перекодировки",
"TaskUpdatePlugins": "Обновление плагинов",
- "TaskRefreshPeople": "Подновить людей",
+ "TaskRefreshPeople": "Подновление людей",
"TaskCleanLogs": "Очистка каталога журналов",
"TaskRefreshLibrary": "Сканирование медиатеки",
"TaskRefreshChapterImages": "Извлечение изображений сцен",
@@ -115,10 +115,10 @@
"TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.",
"TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе.",
"TaskCleanActivityLogDescription": "Удаляет записи журнала активности старше установленного возраста.",
- "TaskCleanActivityLog": "Очистить журнал активности",
+ "TaskCleanActivityLog": "Очистка журнала активности",
"Undefined": "Не определено",
"Forced": "Форсир-ые",
"Default": "По умолчанию",
"TaskOptimizeDatabaseDescription": "Сжимает базу данных и вырезает свободные места. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.",
- "TaskOptimizeDatabase": "Оптимизировать базу данных"
+ "TaskOptimizeDatabase": "Оптимизация базы данных"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json
index e31208e80..72e125dfe 100644
--- a/Emby.Server.Implementations/Localization/Core/sr.json
+++ b/Emby.Server.Implementations/Localization/Core/sr.json
@@ -118,5 +118,6 @@
"Undefined": "Недефинисано",
"Forced": "Принудно",
"Default": "Подразумевано",
- "TaskOptimizeDatabase": "Оптимизуј датабазу"
+ "TaskOptimizeDatabase": "Оптимизуј датабазу",
+ "TaskOptimizeDatabaseDescription": "Сажима базу података и скраћује слободан простор. Покретање овог задатка након скенирања библиотеке или других промена које подразумевају измене базе података које могу побољшати перформансе."
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index f3f601661..5d05361b0 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -16,7 +16,7 @@
"Folders": "Mappar",
"Genres": "Genrer",
"HeaderAlbumArtists": "Albumsartister",
- "HeaderContinueWatching": "Fortsätt kolla",
+ "HeaderContinueWatching": "Fortsätt kolla på",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister",
"HeaderFavoriteEpisodes": "Favoritavsnitt",
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index e26010423..bed67fa4f 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -117,5 +117,7 @@
"TaskCleanActivityLogDescription": "ลบบันทึกกิจกรรมที่เก่ากว่าค่าที่กำหนดไว้",
"TaskCleanActivityLog": "ล้างบันทึกกิจกรรม",
"Undefined": "ไม่ได้กำหนด",
- "Forced": "บังคับใช้"
+ "Forced": "บังคับใช้",
+ "TaskOptimizeDatabase": "ปรับปรุงประสิทธิภาพฐานข้อมูล",
+ "TaskOptimizeDatabaseDescription": "ลดขนาดการจัดเก็บฐานข้อมูล ใช้งานคำสั่งนี้หลังจากสแกนไลบรารีหรือหลังจากการเปลี่ยนแปลงฐานข้อมูล อาจจะทำให้ระบบทำงานเร็วขึ้น"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json
index 73c60e263..e7f3e492c 100644
--- a/Emby.Server.Implementations/Localization/Core/ur_PK.json
+++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json
@@ -113,5 +113,7 @@
"CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
"Application": "پروگرام",
"AppDeviceValues": "پروگرام:{0}, ڈیوائس:{1}",
- "Forced": "جَبری"
+ "Forced": "جَبری",
+ "Undefined": "غير وضاحتى",
+ "Default": "طے شدہ"
}
diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json
index b7ece8d5f..d80f1760d 100644
--- a/Emby.Server.Implementations/Localization/Core/vi.json
+++ b/Emby.Server.Implementations/Localization/Core/vi.json
@@ -13,7 +13,7 @@
"Songs": "Bài Hát",
"Sync": "Đồng Bộ",
"ValueSpecialEpisodeName": "Đặc Biệt - {0}",
- "Albums": "Tuyển Tập",
+ "Albums": "",
"Artists": "Ca Sĩ",
"TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.",
"TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu",
diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
index 9481e26f7..02df2fffe 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
@@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.Playlists
var parentFolder = GetPlaylistsFolder(Guid.Empty);
if (parentFolder == null)
{
- throw new ArgumentException();
+ throw new ArgumentException(nameof(parentFolder));
}
if (string.IsNullOrEmpty(options.MediaType))
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index 2a14a8c7b..a085ee546 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Net;
@@ -50,16 +51,10 @@ namespace Emby.Server.Implementations.Session
/// </summary>
private readonly object _webSocketsLock = new object();
- /// <summary>
- /// The _session manager.
- /// </summary>
private readonly ISessionManager _sessionManager;
-
- /// <summary>
- /// The _logger.
- /// </summary>
private readonly ILogger<SessionWebSocketListener> _logger;
private readonly ILoggerFactory _loggerFactory;
+ private readonly IAuthorizationContext _authorizationContext;
/// <summary>
/// The KeepAlive cancellation token.
@@ -72,14 +67,17 @@ namespace Emby.Server.Implementations.Session
/// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param>
/// <param name="loggerFactory">The logger factory.</param>
+ /// <param name="authorizationContext">The authorization context.</param>
public SessionWebSocketListener(
ILogger<SessionWebSocketListener> logger,
ISessionManager sessionManager,
- ILoggerFactory loggerFactory)
+ ILoggerFactory loggerFactory,
+ IAuthorizationContext authorizationContext)
{
_logger = logger;
_sessionManager = sessionManager;
_loggerFactory = loggerFactory;
+ _authorizationContext = authorizationContext;
}
/// <inheritdoc />
@@ -97,9 +95,9 @@ namespace Emby.Server.Implementations.Session
=> Task.CompletedTask;
/// <inheritdoc />
- public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection)
+ public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection, HttpContext httpContext)
{
- var session = await GetSession(connection.QueryString, connection.RemoteEndPoint.ToString()).ConfigureAwait(false);
+ var session = await GetSession(httpContext, connection.RemoteEndPoint?.ToString()).ConfigureAwait(false);
if (session != null)
{
EnsureController(session, connection);
@@ -107,25 +105,28 @@ namespace Emby.Server.Implementations.Session
}
else
{
- _logger.LogWarning("Unable to determine session based on query string: {0}", connection.QueryString);
+ _logger.LogWarning("Unable to determine session based on query string: {0}", httpContext.Request.QueryString);
}
}
- private Task<SessionInfo> GetSession(IQueryCollection queryString, string remoteEndpoint)
+ private async Task<SessionInfo> GetSession(HttpContext httpContext, string remoteEndpoint)
{
- if (queryString == null)
+ var authorizationInfo = await _authorizationContext.GetAuthorizationInfo(httpContext)
+ .ConfigureAwait(false);
+
+ if (!authorizationInfo.IsAuthenticated)
{
return null;
}
- var token = queryString["api_key"];
- if (string.IsNullOrWhiteSpace(token))
+ var deviceId = authorizationInfo.DeviceId;
+ if (httpContext.Request.Query.TryGetValue("deviceId", out var queryDeviceId))
{
- return null;
+ deviceId = queryDeviceId;
}
- var deviceId = queryString["deviceId"];
- return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint);
+ return await _sessionManager.GetSessionByAuthenticationToken(authorizationInfo.Token, deviceId, remoteEndpoint)
+ .ConfigureAwait(false);
}
private void EnsureController(SessionInfo session, IWebSocketConnection connection)
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 5dbe9f44d..c994ffc90 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.TV
new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { BaseItemKind.Episode },
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
+ OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
SeriesPresentationUniqueKey = presentationUniqueKey,
Limit = limit,
DtoOptions = new DtoOptions { Fields = new[] { ItemFields.SeriesPresentationUniqueKey }, EnableImages = false },
@@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.TV
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { BaseItemKind.Episode },
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) },
+ OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Descending) },
IsPlayed = true,
Limit = 1,
ParentIndexNumberNotEquals = 0,
@@ -211,7 +211,7 @@ namespace Emby.Server.Implementations.TV
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { BaseItemKind.Episode },
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) },
+ OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
Limit = 1,
IsPlayed = false,
IsVirtualItem = false,
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index 33e4e5651..c8ab99de4 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -97,21 +97,11 @@ namespace Emby.Server.Implementations.Udp
private async Task BeginReceiveAsync(CancellationToken cancellationToken)
{
- var infiniteTask = Task.Delay(-1, cancellationToken);
while (!cancellationToken.IsCancellationRequested)
{
try
{
- var task = _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint);
- await Task.WhenAny(task, infiniteTask).ConfigureAwait(false);
-
- if (!task.IsCompleted)
- {
- return;
- }
-
- var result = task.Result;
-
+ var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint, cancellationToken).ConfigureAwait(false);
var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes);
if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
{
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 24d592525..5eb4c9ffa 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Updates
/// <summary>
/// The current installations.
/// </summary>
- private readonly List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations;
+ private readonly List<(InstallationInfo Info, CancellationTokenSource Token)> _currentInstallations;
/// <summary>
/// The completed installations.
@@ -399,13 +399,13 @@ namespace Emby.Server.Implementations.Updates
{
lock (_currentInstallationsLock)
{
- var install = _currentInstallations.Find(x => x.info.Id == id);
+ var install = _currentInstallations.Find(x => x.Info.Id == id);
if (install == default((InstallationInfo, CancellationTokenSource)))
{
return false;
}
- install.token.Cancel();
+ install.Token.Cancel();
_currentInstallations.Remove(install);
return true;
}
diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs
index 4e8c01577..b1c576c33 100644
--- a/Jellyfin.Api/Controllers/DlnaServerController.cs
+++ b/Jellyfin.Api/Controllers/DlnaServerController.cs
@@ -9,6 +9,7 @@ using Emby.Dlna.Main;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -337,11 +338,7 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- var contentType = "image/" + Path.GetExtension(fileName)
- .TrimStart('.')
- .ToLowerInvariant();
-
- return File(icon.Stream, contentType);
+ return File(icon.Stream, MimeTypes.GetMimeType(fileName));
}
private string GetAbsoluteUri()
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index e170436d1..a4f12666d 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -197,16 +197,16 @@ namespace Jellyfin.Api.Controllers
{
filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair
{
- Name = i.Item1.Name,
- Id = i.Item1.Id
+ Name = i.Item.Name,
+ Id = i.Item.Id
}).ToArray();
}
else
{
filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair
{
- Name = i.Item1.Name,
- Id = i.Item1.Id
+ Name = i.Item.Name,
+ Id = i.Item.Id
}).ToArray();
}
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index 86933074d..e72589cfa 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -1878,8 +1878,8 @@ namespace Jellyfin.Api.Controllers
if (!supportsWebP)
{
var userAgent = Request.Headers[HeaderNames.UserAgent].ToString();
- if (userAgent.IndexOf("crosswalk", StringComparison.OrdinalIgnoreCase) != -1 &&
- userAgent.IndexOf("android", StringComparison.OrdinalIgnoreCase) != -1)
+ if (userAgent.Contains("crosswalk", StringComparison.OrdinalIgnoreCase)
+ && userAgent.Contains("android", StringComparison.OrdinalIgnoreCase))
{
supportsWebP = true;
}
@@ -1905,10 +1905,7 @@ namespace Jellyfin.Api.Controllers
private bool SupportsFormat(IReadOnlyCollection<string> requestAcceptTypes, string acceptParam, ImageFormat format, bool acceptAll)
{
- var normalized = format.ToString().ToLowerInvariant();
- var mimeType = "image/" + normalized;
-
- if (requestAcceptTypes.Contains(mimeType))
+ if (requestAcceptTypes.Contains(format.GetMimeType()))
{
return true;
}
@@ -1918,6 +1915,8 @@ namespace Jellyfin.Api.Controllers
return true;
}
+ // Review if this should be jpeg, jpg or both for ImageFormat.Jpg
+ var normalized = format.ToString().ToLowerInvariant();
return string.Equals(acceptParam, normalized, StringComparison.OrdinalIgnoreCase);
}
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 65c0662d2..f8192955e 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -228,9 +228,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
- var user = !userId.Equals(Guid.Empty)
- ? _userManager.GetUserById(userId)
- : null;
+ var user = userId == Guid.Empty ? null : _userManager.GetUserById(userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -484,7 +482,7 @@ namespace Jellyfin.Api.Controllers
// Albums by artist
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) };
+ query.OrderBy = new[] { (ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending) };
}
}
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index ca8bc0bdd..2cfd36d00 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -30,7 +30,7 @@ namespace Jellyfin.Api.Helpers
{
if (sortBy.Count == 0)
{
- return Array.Empty<ValueTuple<string, SortOrder>>();
+ return Array.Empty<(string, SortOrder)>();
}
var result = new (string, SortOrder)[sortBy.Count];
@@ -104,7 +104,7 @@ namespace Jellyfin.Api.Helpers
}
internal static QueryResult<BaseItemDto> CreateQueryResult(
- QueryResult<(BaseItem, ItemCounts)> result,
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> result,
DtoOptions dtoOptions,
IDtoService dtoService,
bool includeItemTypes,
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 3d0a51ff6..c41b343c7 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -394,12 +394,12 @@ namespace Jellyfin.Server.Implementations.Users
var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint)
.ConfigureAwait(false);
- var authenticationProvider = authResult.authenticationProvider;
- var success = authResult.success;
+ var authenticationProvider = authResult.AuthenticationProvider;
+ var success = authResult.Success;
if (user == null)
{
- string updatedUsername = authResult.username;
+ string updatedUsername = authResult.Username;
if (success
&& authenticationProvider != null
@@ -785,7 +785,7 @@ namespace Jellyfin.Server.Implementations.Users
return providers;
}
- private async Task<(IAuthenticationProvider? authenticationProvider, string username, bool success)> AuthenticateLocalUser(
+ private async Task<(IAuthenticationProvider? AuthenticationProvider, string Username, bool Success)> AuthenticateLocalUser(
string username,
string password,
User? user,
@@ -798,8 +798,8 @@ namespace Jellyfin.Server.Implementations.Users
{
var providerAuthResult =
await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
- var updatedUsername = providerAuthResult.username;
- success = providerAuthResult.success;
+ var updatedUsername = providerAuthResult.Username;
+ success = providerAuthResult.Success;
if (success)
{
@@ -822,7 +822,7 @@ namespace Jellyfin.Server.Implementations.Users
return (authenticationProvider, username, success);
}
- private async Task<(string username, bool success)> AuthenticateWithProvider(
+ private async Task<(string Username, bool Success)> AuthenticateWithProvider(
IAuthenticationProvider provider,
string username,
string password,
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index eb82a9d2b..12efa15dd 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -47,7 +47,7 @@
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
- <PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
+ <PackageReference Include="Serilog.Sinks.Graylog" Version="2.3.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" />
</ItemGroup>
diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs
index a951f751e..5e601ca84 100644
--- a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs
+++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs
@@ -53,7 +53,7 @@ public class CreateNetworkConfiguration : IMigrationRoutine
networkConfigSerializer.Serialize(xmlWriter, networkSettings);
}
-#pragma warning disable CS1591
+#pragma warning disable
public sealed class OldNetworkConfiguration
{
public const int DefaultHttpPort = 8096;
@@ -134,5 +134,5 @@ public class CreateNetworkConfiguration : IMigrationRoutine
public string[] KnownProxies { get; set; } = Array.Empty<string>();
}
-#pragma warning restore CS1591
+#pragma warning restore
}
diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs
index f6e3971bf..f1428d4be 100644
--- a/MediaBrowser.Common/Net/IPNetAddress.cs
+++ b/MediaBrowser.Common/Net/IPNetAddress.cs
@@ -195,7 +195,7 @@ namespace MediaBrowser.Common.Net
return NetworkAddress.PrefixLength <= netaddrObj.PrefixLength;
}
- var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength).address;
+ var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength).Address;
return NetworkAddress.Address.Equals(altAddress);
}
diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs
index 2612268fd..bd5368882 100644
--- a/MediaBrowser.Common/Net/IPObject.cs
+++ b/MediaBrowser.Common/Net/IPObject.cs
@@ -53,7 +53,7 @@ namespace MediaBrowser.Common.Net
/// <param name="address">IP Address to convert.</param>
/// <param name="prefixLength">Subnet prefix.</param>
/// <returns>IPAddress.</returns>
- public static (IPAddress address, byte prefixLength) NetworkAddressOf(IPAddress address, byte prefixLength)
+ public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength)
{
if (address == null)
{
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index 7ca0e851b..03882a0b9 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -75,7 +75,7 @@ namespace MediaBrowser.Controller.Drawing
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task.</returns>
- Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options);
+ Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options);
/// <summary>
/// Gets the supported image output formats.
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 9d0187c8c..29f7bf92b 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -135,7 +135,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return info;
}
- protected override IEnumerable<(BaseItem, MediaSourceType)> GetAllItemsForMediaSources()
+ protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
=> new[] { ((BaseItem)this, MediaSourceType.Default) };
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index f5dd82548..915971adc 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1069,7 +1069,7 @@ namespace MediaBrowser.Controller.Entities
}
var list = GetAllItemsForMediaSources();
- var result = list.Select(i => GetVersionInfo(enablePathSubstitution, i.Item1, i.Item2)).ToList();
+ var result = list.Select(i => GetVersionInfo(enablePathSubstitution, i.Item, i.MediaSourceType)).ToList();
if (IsActiveRecording())
{
@@ -1097,7 +1097,7 @@ namespace MediaBrowser.Controller.Entities
.ToList();
}
- protected virtual IEnumerable<(BaseItem, MediaSourceType)> GetAllItemsForMediaSources()
+ protected virtual IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
{
return Enumerable.Empty<(BaseItem, MediaSourceType)>();
}
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index f06b5c787..db1697c79 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Entities
MediaTypes = Array.Empty<string>();
MinSimilarityScore = 20;
OfficialRatings = Array.Empty<string>();
- OrderBy = Array.Empty<ValueTuple<string, SortOrder>>();
+ OrderBy = Array.Empty<(string, SortOrder)>();
PersonIds = Array.Empty<Guid>();
PersonTypes = Array.Empty<string>();
PresetViews = Array.Empty<string>();
@@ -271,7 +271,7 @@ namespace MediaBrowser.Controller.Entities
public bool? HasChapterImages { get; set; }
- public IReadOnlyList<(string, SortOrder)> OrderBy { get; set; }
+ public IReadOnlyList<(string OrderBy, SortOrder SortOrder)> OrderBy { get; set; }
public DateTime? MinDateCreated { get; set; }
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 6b93d8d87..882abc927 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -33,6 +33,7 @@ namespace MediaBrowser.Controller.Entities.Movies
public override bool SupportsPeople => true;
/// <inheritdoc />
+ [JsonIgnore]
public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
.ToArray();
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index dfaf03fda..77e70f8fb 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -20,12 +20,14 @@ namespace MediaBrowser.Controller.Entities.Movies
public class Movie : Video, IHasSpecialFeatures, IHasTrailers, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping
{
/// <inheritdoc />
+ [JsonIgnore]
public IReadOnlyList<Guid> SpecialFeatureIds => GetExtras()
.Where(extra => extra.ExtraType != null && extra is Video)
.Select(extra => extra.Id)
.ToArray();
/// <inheritdoc />
+ [JsonIgnore]
public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
.ToArray();
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index dcc752f8c..c8a0e21eb 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -11,6 +11,7 @@ using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.TV
@@ -21,6 +22,7 @@ namespace MediaBrowser.Controller.Entities.TV
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
{
/// <inheritdoc />
+ [JsonIgnore]
public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
.ToArray();
@@ -336,5 +338,22 @@ namespace MediaBrowser.Controller.Entities.TV
return hasChanges;
}
+
+ public override List<ExternalUrl> GetRelatedUrls()
+ {
+ var list = base.GetRelatedUrls();
+
+ var imdbId = this.GetProviderId(MetadataProvider.Imdb);
+ if (!string.IsNullOrEmpty(imdbId))
+ {
+ list.Add(new ExternalUrl
+ {
+ Name = "Trakt",
+ Url = string.Format(CultureInfo.InvariantCulture, "https://trakt.tv/episodes/{0}", imdbId)
+ });
+ }
+
+ return list;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index bdadc2775..a3c4a81fd 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -50,6 +50,7 @@ namespace MediaBrowser.Controller.Entities.TV
public override bool SupportsPeople => true;
/// <inheritdoc />
+ [JsonIgnore]
public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
.ToArray();
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 4f7614f96..3e125602a 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -517,7 +517,7 @@ namespace MediaBrowser.Controller.Entities
}).FirstOrDefault();
}
- protected override IEnumerable<(BaseItem, MediaSourceType)> GetAllItemsForMediaSources()
+ protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
{
var list = new List<(BaseItem, MediaSourceType)>
{
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index eba92695e..8db528330 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.Library
/// <returns>IEnumerable{BaseItem}.</returns>
IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, SortOrder sortOrder);
- IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy);
+ IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy);
/// <summary>
/// Gets the user root folder.
@@ -573,17 +573,17 @@ namespace MediaBrowser.Controller.Library
void RemoveMediaPath(string virtualFolderName, string mediaPath);
- QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query);
int GetCount(InternalItemsQuery query);
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index dbd18165d..6dc5665b2 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -251,7 +251,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="fields">The fields.</param>
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
- Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
+ Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
/// <summary>
/// Saves the tuner host.
@@ -292,7 +292,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="items">The items.</param>
/// <param name="options">The options.</param>
/// <param name="user">The user.</param>
- void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user);
+ void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user);
Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 8c5474539..bde10dbbf 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -11,6 +11,7 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
@@ -21,6 +22,13 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public class EncodingHelper
{
+ private const string QsvAlias = "qs";
+ private const string VaapiAlias = "va";
+ private const string D3d11vaAlias = "dx11";
+ private const string VideotoolboxAlias = "vt";
+ private const string OpenclAlias = "ocl";
+ private const string CudaAlias = "cu";
+
private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder;
@@ -42,13 +50,6 @@ namespace MediaBrowser.Controller.MediaEncoding
"Main10"
};
- private const string QsvAlias = "qs";
- private const string VaapiAlias = "va";
- private const string D3d11vaAlias = "dx11";
- private const string VideotoolboxAlias = "vt";
- private const string OpenclAlias = "ocl";
- private const string CudaAlias = "cu";
-
public EncodingHelper(
IMediaEncoder mediaEncoder,
ISubtitleEncoder subtitleEncoder)
@@ -885,6 +886,13 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream != null && state.AudioStream.IsExternal)
{
+ // Also seek the external audio stream.
+ var seekAudioParam = GetFastSeekCommandLineParameter(state, options);
+ if (!string.IsNullOrEmpty(seekAudioParam))
+ {
+ arg.Append(' ').Append(seekAudioParam);
+ }
+
arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
}
@@ -1278,7 +1286,7 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -pix_fmt nv21";
}
- var isVc1 = string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
+ var isVc1 = string.Equals(state.VideoStream?.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
@@ -1318,7 +1326,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" };
- if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparer.OrdinalIgnoreCase))
+ if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparison.OrdinalIgnoreCase))
{
param += " -preset " + encodingOptions.EncoderPreset;
}
@@ -1669,7 +1677,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Source and target codecs must match
if (string.IsNullOrEmpty(videoStream.Codec)
- || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase))
+ || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return false;
}
@@ -1687,7 +1695,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var requestedProfile = requestedProfiles[0];
// strip spaces because they may be stripped out on the query string as well
if (!string.IsNullOrEmpty(videoStream.Profile)
- && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordinal), StringComparer.OrdinalIgnoreCase))
+ && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordinal), StringComparison.OrdinalIgnoreCase))
{
var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
@@ -1794,7 +1802,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Source and target codecs must match
if (string.IsNullOrEmpty(audioStream.Codec)
- || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
+ || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return false;
}
@@ -2290,7 +2298,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
}
- public static (int? width, int? height) GetFixedOutputSize(
+ public static (int? Width, int? Height) GetFixedOutputSize(
int? videoWidth,
int? videoHeight,
int? requestedWidth,
@@ -2671,7 +2679,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="options">Encoding options.</param>
/// <param name="vidEncoder">Video encoder to use.</param>
/// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
- public (List<string>, List<string>, List<string>) GetSwVidFilterChain(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
EncodingJobInfo state,
EncodingOptions options,
string vidEncoder)
@@ -2751,7 +2759,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="options">Encoding options.</param>
/// <param name="vidEncoder">Video encoder to use.</param>
/// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
- public (List<string>, List<string>, List<string>) GetNvidiaVidFilterChain(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
EncodingJobInfo state,
EncodingOptions options,
string vidEncoder)
@@ -2778,7 +2786,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
- public (List<string>, List<string>, List<string>) GetNvidiaVidFiltersPrefered(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefered(
EncodingJobInfo state,
EncodingOptions options,
string vidDecoder,
@@ -2838,6 +2846,7 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add("hwupload");
}
}
+
if (isNvdecDecoder)
{
// INPUT cuda surface(vram)
@@ -2938,7 +2947,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="options">Encoding options.</param>
/// <param name="vidEncoder">Video encoder to use.</param>
/// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
- public (List<string>, List<string>, List<string>) GetAmdVidFilterChain(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
EncodingJobInfo state,
EncodingOptions options,
string vidEncoder)
@@ -2966,7 +2975,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
- public (List<string>, List<string>, List<string>) GetAmdDx11VidFiltersPrefered(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPrefered(
EncodingJobInfo state,
EncodingOptions options,
string vidDecoder,
@@ -3136,7 +3145,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="options">Encoding options.</param>
/// <param name="vidEncoder">Video encoder to use.</param>
/// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
- public (List<string>, List<string>, List<string>) GetIntelVidFilterChain(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
EncodingJobInfo state,
EncodingOptions options,
string vidEncoder)
@@ -3182,7 +3191,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return (null, null, null);
}
- public (List<string>, List<string>, List<string>) GetIntelQsvDx11VidFiltersPrefered(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFiltersPrefered(
EncodingJobInfo state,
EncodingOptions options,
string vidDecoder,
@@ -3374,7 +3383,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return (mainFilters, subFilters, overlayFilters);
}
- public (List<string>, List<string>, List<string>) GetIntelQsvVaapiVidFiltersPrefered(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFiltersPrefered(
EncodingJobInfo state,
EncodingOptions options,
string vidDecoder,
@@ -3589,7 +3598,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="options">Encoding options.</param>
/// <param name="vidEncoder">Video encoder to use.</param>
/// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
- public (List<string>, List<string>, List<string>) GetVaapiVidFilterChain(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
EncodingJobInfo state,
EncodingOptions options,
string vidEncoder)
@@ -3615,13 +3624,13 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!isSwEncoder)
{
var newfilters = new List<string>();
- var noOverlay = swFilterChain.Item3.Count == 0;
- newfilters.AddRange(noOverlay ? swFilterChain.Item1 : swFilterChain.Item3);
+ var noOverlay = swFilterChain.OverlayFilters.Count == 0;
+ newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
newfilters.Add("hwupload");
- var mainFilters = noOverlay ? newfilters : swFilterChain.Item1;
- var overlayFilters = noOverlay ? swFilterChain.Item3 : newfilters;
- return (mainFilters, swFilterChain.Item2, overlayFilters);
+ var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
+ var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
+ return (mainFilters, swFilterChain.SubFilters, overlayFilters);
}
return swFilterChain;
@@ -3638,7 +3647,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
- public (List<string>, List<string>, List<string>) GetVaapiFullVidFiltersPrefered(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiFullVidFiltersPrefered(
EncodingJobInfo state,
EncodingOptions options,
string vidDecoder,
@@ -3834,7 +3843,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return (mainFilters, subFilters, overlayFilters);
}
- public (List<string>, List<string>, List<string>) GetVaapiLimitedVidFiltersPrefered(
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFiltersPrefered(
EncodingJobInfo state,
EncodingOptions options,
string vidDecoder,
@@ -4090,7 +4099,6 @@ namespace MediaBrowser.Controller.MediaEncoding
"{0}",
string.Join(',', overlayFilters));
-
var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
var subtitleStreamIndex = state.SubtitleStream.IsExternal
? 0
@@ -4302,11 +4310,19 @@ namespace MediaBrowser.Controller.MediaEncoding
var decoderName = decoderPrefix + '_' + decoderSuffix;
- var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
+ var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
if (bitDepth == 10 && isCodecAvailable)
{
- if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc)
- || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9))
+ if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
+ && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
+ && !options.EnableDecodingColorDepth10Hevc)
+ {
+ return null;
+ }
+
+ if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
+ && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
+ && !options.EnableDecodingColorDepth10Vp9)
{
return null;
}
@@ -4344,15 +4360,23 @@ namespace MediaBrowser.Controller.MediaEncoding
var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
- var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
+ var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
// Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
var isAv1 = string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
if (bitDepth == 10 && isCodecAvailable)
{
- if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc)
- || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9))
+ if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
+ && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
+ && !options.EnableDecodingColorDepth10Hevc)
+ {
+ return null;
+ }
+
+ if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
+ && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
+ && !options.EnableDecodingColorDepth10Vp9)
{
return null;
}
@@ -5072,12 +5096,12 @@ namespace MediaBrowser.Controller.MediaEncoding
// Transcoding to 2ch ac3 almost always causes a playback failure
// Keep it in the supported codecs list, but shift it to the end of the list so that if transcoding happens, another codec is used
var shiftAudioCodecs = new[] { "ac3", "eac3" };
- if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparer.OrdinalIgnoreCase)))
+ if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
{
return;
}
- while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparer.OrdinalIgnoreCase))
+ while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
{
var removed = shiftAudioCodecs[0];
audioCodecs.RemoveAt(0);
@@ -5100,12 +5124,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var shiftVideoCodecs = new[] { "hevc", "h265" };
- if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparer.OrdinalIgnoreCase)))
+ if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
{
return;
}
- while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparer.OrdinalIgnoreCase))
+ while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
{
var removed = shiftVideoCodecs[0];
videoCodecs.RemoveAt(0);
diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs
index c38e7ec3b..4e7e26624 100644
--- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs
@@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public interface IAttachmentExtractor
{
- Task<(MediaAttachment attachment, Stream stream)> GetAttachment(
+ Task<(MediaAttachment Attachment, Stream Stream)> GetAttachment(
BaseItem item,
string mediaSourceId,
int attachmentStreamIndex,
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 27d618a3f..fd3eb8105 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -32,19 +32,19 @@ namespace MediaBrowser.Controller.MediaEncoding
Version EncoderVersion { get; }
/// <summary>
- /// Whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver).
+ /// Gets a value indicating whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver).
/// </summary>
/// <value><c>true</c> if the Vaapi device is an AMD(radeonsi/r600 Mesa driver) GPU, <c>false</c> otherwise.</value>
bool IsVaapiDeviceAmd { get; }
/// <summary>
- /// Whether the configured Vaapi device is from Intel(iHD driver).
+ /// Gets a value indicating whether the configured Vaapi device is from Intel(iHD driver).
/// </summary>
/// <value><c>true</c> if the Vaapi device is an Intel(iHD driver) GPU, <c>false</c> otherwise.</value>
bool IsVaapiDeviceInteliHD { get; }
/// <summary>
- /// Whether the configured Vaapi device is from Intel(legacy i965 driver).
+ /// Gets a value indicating whether the configured Vaapi device is from Intel(legacy i965 driver).
/// </summary>
/// <value><c>true</c> if the Vaapi device is an Intel(legacy i965 driver) GPU, <c>false</c> otherwise.</value>
bool IsVaapiDeviceInteli965 { get; }
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index 933f440ac..8b2837ee3 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.MediaEncoding
break;
}
- await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+ await target.WriteAsync(bytes).ConfigureAwait(false);
// Check again, the stream could have been closed
if (!target.CanWrite)
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index 0813a8e7d..eadc09fd4 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
+using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Net
@@ -95,7 +96,7 @@ namespace MediaBrowser.Controller.Net
}
/// <inheritdoc />
- public Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection) => Task.CompletedTask;
+ public Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection, HttpContext httpContext) => Task.CompletedTask;
/// <summary>
/// Starts sending messages over a web socket.
diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
index c8c5caf80..2c6483ae2 100644
--- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
@@ -30,12 +30,6 @@ namespace MediaBrowser.Controller.Net
DateTime LastKeepAliveDate { get; set; }
/// <summary>
- /// Gets the query string.
- /// </summary>
- /// <value>The query string.</value>
- IQueryCollection QueryString { get; }
-
- /// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
diff --git a/MediaBrowser.Controller/Net/IWebSocketListener.cs b/MediaBrowser.Controller/Net/IWebSocketListener.cs
index f1a75d518..672bb8cbf 100644
--- a/MediaBrowser.Controller/Net/IWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketListener.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
@@ -18,7 +19,8 @@ namespace MediaBrowser.Controller.Net
/// Processes a new web socket connection.
/// </summary>
/// <param name="connection">An instance of the <see cref="IWebSocketConnection"/> interface.</param>
+ /// <param name="httpContext">The current http context.</param>
/// <returns>Task.</returns>
- Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection);
+ Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection, HttpContext httpContext);
}
}
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index a084f9196..837bf0bb2 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -161,17 +161,17 @@ namespace MediaBrowser.Controller.Persistence
int GetCount(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query);
List<string> GetMusicGenreNames();
diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs
index 2085ae4ad..58a0fa2a9 100644
--- a/MediaBrowser.Controller/Providers/MetadataResult.cs
+++ b/MediaBrowser.Controller/Providers/MetadataResult.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Providers
{
// Images aren't always used so the allocation is a waste a lot of the time
private List<LocalImageInfo> _images;
- private List<(string url, ImageType type)> _remoteImages;
+ private List<(string Url, ImageType Type)> _remoteImages;
public MetadataResult()
{
@@ -27,9 +27,9 @@ namespace MediaBrowser.Controller.Providers
set => _images = value;
}
- public List<(string url, ImageType type)> RemoteImages
+ public List<(string Url, ImageType Type)> RemoteImages
{
- get => _remoteImages ??= new List<(string url, ImageType type)>();
+ get => _remoteImages ??= new List<(string Url, ImageType Type)>();
set => _remoteImages = value;
}
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index 9ebc0d0cf..3fd4cd731 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -50,7 +50,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
}
/// <inheritdoc />
- public async Task<(MediaAttachment attachment, Stream stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken)
+ public async Task<(MediaAttachment Attachment, Stream Stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken)
{
if (item == null)
{
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index fce71bf1a..e1643ea43 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -91,9 +91,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc />
public string EncoderPath => _ffmpegPath;
+
public Version EncoderVersion => _ffmpegVersion;
+
public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd;
+
public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD;
+
public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965;
/// <summary>
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 89365a516..5b1ec8041 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -139,28 +139,28 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var subtitle = await GetSubtitleStream(mediaSource, subtitleStream, cancellationToken)
.ConfigureAwait(false);
- var inputFormat = subtitle.format;
+ var inputFormat = subtitle.Format;
// Return the original if we don't have any way of converting it
if (!TryGetWriter(outputFormat, out var writer))
{
- return subtitle.stream;
+ return subtitle.Stream;
}
// Return the original if the same format is being requested
// Character encoding was already handled in GetSubtitleStream
if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
{
- return subtitle.stream;
+ return subtitle.Stream;
}
- using (var stream = subtitle.stream)
+ using (var stream = subtitle.Stream)
{
return ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken);
}
}
- private async Task<(Stream stream, string format)> GetSubtitleStream(
+ private async Task<(Stream Stream, string Format)> GetSubtitleStream(
MediaSourceInfo mediaSource,
MediaStream subtitleStream,
CancellationToken cancellationToken)
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index a19017215..d2ca21150 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -289,8 +289,8 @@ namespace MediaBrowser.Model.Dlna
var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options);
- var directPlayMethods = directPlayInfo.Item1;
- var transcodeReasons = directPlayInfo.Item2.ToList();
+ var directPlayMethods = directPlayInfo.PlayMethods;
+ var transcodeReasons = directPlayInfo.TranscodeReasons.ToList();
int? inputAudioChannels = audioStream?.Channels;
int? inputAudioBitrate = audioStream?.BitDepth;
@@ -448,7 +448,7 @@ namespace MediaBrowser.Model.Dlna
return options.GetMaxBitrate(isAudio);
}
- private (IEnumerable<PlayMethod>, IEnumerable<TranscodeReason>) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
+ private (IEnumerable<PlayMethod> PlayMethods, IEnumerable<TranscodeReason> TranscodeReasons) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
{
DirectPlayProfile directPlayProfile = options.Profile.DirectPlayProfiles
.FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream));
@@ -679,8 +679,8 @@ namespace MediaBrowser.Model.Dlna
// 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, audioStream, options, PlayMethod.DirectPlay);
var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectStream);
- bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.directPlay);
- bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.directPlay);
+ bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.DirectPlay);
+ bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.DirectPlay);
_logger.LogDebug(
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
@@ -695,7 +695,7 @@ namespace MediaBrowser.Model.Dlna
{
// See if it can be direct played
var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectStream);
- var directPlay = directPlayInfo.playMethod;
+ var directPlay = directPlayInfo.PlayMethod;
if (directPlay != null)
{
@@ -713,17 +713,17 @@ namespace MediaBrowser.Model.Dlna
return playlistItem;
}
- transcodeReasons.AddRange(directPlayInfo.transcodeReasons);
+ transcodeReasons.AddRange(directPlayInfo.TranscodeReasons);
}
- if (directPlayEligibilityResult.reason.HasValue)
+ if (directPlayEligibilityResult.Reason.HasValue)
{
- transcodeReasons.Add(directPlayEligibilityResult.reason.Value);
+ transcodeReasons.Add(directPlayEligibilityResult.Reason.Value);
}
- if (directStreamEligibilityResult.reason.HasValue)
+ if (directStreamEligibilityResult.Reason.HasValue)
{
- transcodeReasons.Add(directStreamEligibilityResult.reason.Value);
+ transcodeReasons.Add(directStreamEligibilityResult.Reason.Value);
}
// Can't direct play, find the transcoding profile
@@ -1000,7 +1000,7 @@ namespace MediaBrowser.Model.Dlna
return 7168000;
}
- private (PlayMethod? playMethod, List<TranscodeReason> transcodeReasons) GetVideoDirectPlayProfile(
+ private (PlayMethod? PlayMethod, List<TranscodeReason> TranscodeReasons) GetVideoDirectPlayProfile(
VideoOptions options,
MediaSourceInfo mediaSource,
MediaStream videoStream,
@@ -1209,7 +1209,7 @@ namespace MediaBrowser.Model.Dlna
mediaSource.Path ?? "Unknown path");
}
- private (bool directPlay, TranscodeReason? reason) IsEligibleForDirectPlay(
+ private (bool DirectPlay, TranscodeReason? Reason) IsEligibleForDirectPlay(
MediaSourceInfo item,
long maxBitrate,
MediaStream subtitleStream,
@@ -1236,7 +1236,7 @@ namespace MediaBrowser.Model.Dlna
return (false, TranscodeReason.ContainerBitrateExceedsLimit);
}
- if (audioStream.IsExternal)
+ if (audioStream?.IsExternal == true)
{
return (false, TranscodeReason.AudioIsExternal);
}
diff --git a/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs b/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs
new file mode 100644
index 000000000..68a5c2534
--- /dev/null
+++ b/MediaBrowser.Model/Drawing/ImageFormatExtensions.cs
@@ -0,0 +1,27 @@
+using System.ComponentModel;
+using System.Net.Mime;
+
+namespace MediaBrowser.Model.Drawing;
+
+/// <summary>
+/// Extension class for the <see cref="ImageFormat" /> enum.
+/// </summary>
+public static class ImageFormatExtensions
+{
+ /// <summary>
+ /// Returns the correct mime type for this <see cref="ImageFormat" />.
+ /// </summary>
+ /// <param name="format">This <see cref="ImageFormat" />.</param>
+ /// <exception cref="InvalidEnumArgumentException">The <paramref name="format"/> is an invalid enumeration value.</exception>
+ /// <returns>The correct mime type for this <see cref="ImageFormat" />.</returns>
+ public static string GetMimeType(this ImageFormat format)
+ => format switch
+ {
+ ImageFormat.Bmp => "image/bmp",
+ ImageFormat.Gif => MediaTypeNames.Image.Gif,
+ ImageFormat.Jpg => MediaTypeNames.Image.Jpeg,
+ ImageFormat.Png => "image/png",
+ ImageFormat.Webp => "image/webp",
+ _ => throw new InvalidEnumArgumentException(nameof(format), (int)format, typeof(ImageFormat))
+ };
+}
diff --git a/MediaBrowser.Model/Session/GeneralCommandType.cs b/MediaBrowser.Model/Session/GeneralCommandType.cs
index c58fa9a6b..166a6b441 100644
--- a/MediaBrowser.Model/Session/GeneralCommandType.cs
+++ b/MediaBrowser.Model/Session/GeneralCommandType.cs
@@ -39,7 +39,6 @@ namespace MediaBrowser.Model.Session
SetRepeatMode = 29,
ChannelUp = 30,
ChannelDown = 31,
- SetMaxStreamingBitrate = 31,
Guide = 32,
ToggleStats = 33,
PlayMediaSource = 34,
@@ -48,6 +47,7 @@ namespace MediaBrowser.Model.Session
PlayState = 37,
PlayNext = 38,
ToggleOsdMenu = 39,
- Play = 40
+ Play = 40,
+ SetMaxStreamingBitrate = 41
}
}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 5f133686f..0f21ec7b2 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
@@ -195,7 +196,13 @@ namespace MediaBrowser.Providers.Manager
if (response.HasImage)
{
- if (!string.IsNullOrEmpty(response.Path))
+ if (string.IsNullOrEmpty(response.Path))
+ {
+ var mimeType = response.Format.GetMimeType();
+
+ await _providerManager.SaveImage(item, response.Stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
+ }
+ else
{
if (response.Protocol == MediaProtocol.Http)
{
@@ -218,12 +225,6 @@ namespace MediaBrowser.Providers.Manager
await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
}
}
- else
- {
- var mimeType = "image/" + response.Format.ToString().ToLowerInvariant();
-
- await _providerManager.SaveImage(item, response.Stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
- }
downloadedImages.Add(imageType);
result.UpdateType |= ItemUpdateType.ImageUpdate;
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 667574f39..0c52d2673 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -687,12 +687,12 @@ namespace MediaBrowser.Providers.Manager
{
try
{
- await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false);
+ 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);
+ Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url);
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
index 8a32cb07c..5ae5ff3be 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
@@ -303,7 +303,7 @@ namespace MediaBrowser.Providers.Music
return ReleaseResult.Parse(reader).FirstOrDefault();
}
- private static (string, string) ParseArtistCredit(XmlReader reader)
+ private static (string Name, string ArtistId) ParseArtistCredit(XmlReader reader)
{
reader.MoveToContent();
reader.Read();
@@ -345,7 +345,7 @@ namespace MediaBrowser.Providers.Music
return default;
}
- private static (string, string) ParseArtistNameCredit(XmlReader reader)
+ private static (string Name, string ArtistId) ParseArtistNameCredit(XmlReader reader)
{
reader.MoveToContent();
reader.Read();
@@ -388,7 +388,7 @@ namespace MediaBrowser.Providers.Music
return (null, null);
}
- private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId)
+ private static (string Name, string ArtistId) ParseArtistArtistCredit(XmlReader reader, string artistId)
{
reader.MoveToContent();
reader.Read();
@@ -628,7 +628,7 @@ namespace MediaBrowser.Providers.Music
public string Overview;
public int? Year;
- public List<ValueTuple<string, string>> Artists = new List<ValueTuple<string, string>>();
+ public List<(string, string)> Artists = new();
public static IEnumerable<ReleaseResult> Parse(XmlReader reader)
{
@@ -776,7 +776,7 @@ namespace MediaBrowser.Providers.Music
using var subReader = reader.ReadSubtree();
var artist = ParseArtistCredit(subReader);
- if (!string.IsNullOrEmpty(artist.Item1))
+ if (!string.IsNullOrEmpty(artist.Name))
{
result.Artists.Add(artist);
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
index dec796148..92f5306e5 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
@@ -23,6 +23,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public bool ExcludeTagsMovies { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether season name should be imported from TMDb.
+ /// </summary>
+ public bool ImportSeasonName { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating the maximum number of cast members to fetch for an item.
/// </summary>
public int MaxCastMembers { get; set; } = 15;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
index 52693795b..72bd38ffa 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
@@ -20,6 +20,10 @@
<input is="emby-checkbox" type="checkbox" id="excludeTagsMovies" />
<span>Exclude tags/keywords from metadata fetched for movies.</span>
</label>
+ <label class="checkboxContainer">
+ <input is="emby-checkbox" type="checkbox" id="importSeasonName" />
+ <span>Import season name from metadata fetched for series.</span>
+ </label>
<div class="inputContainer">
<input is="emby-input" type="number" id="maxCastMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Cast Members" />
<div class="fieldDescription">The maximum number of cast members to fetch for an item.</div>
@@ -98,6 +102,7 @@
document.querySelector('#includeAdult').checked = config.IncludeAdult;
document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries;
document.querySelector('#excludeTagsMovies').checked = config.ExcludeTagsMovies;
+ document.querySelector('#importSeasonName').checked = config.ImportSeasonName;
var maxCastMembers = document.querySelector('#maxCastMembers');
maxCastMembers.value = config.MaxCastMembers;
@@ -120,6 +125,7 @@
config.IncludeAdult = document.querySelector('#includeAdult').checked;
config.ExcludeTagsSeries = document.querySelector('#excludeTagsSeries').checked;
config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked;
+ config.ImportSeasonName = document.querySelector('#importSeasonName').checked;
config.MaxCastMembers = document.querySelector('#maxCastMembers').value;
config.PosterSize = document.querySelector('#selectPosterSize').value;
config.BackdropSize = document.querySelector('#selectBackdropSize').value;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
index 27c52a5a2..64ed3f408 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
@@ -59,6 +59,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
Overview = seasonResult.Overview
};
+ if (Plugin.Instance.Configuration.ImportSeasonName)
+ {
+ result.Item.Name = seasonResult.Name;
+ }
+
if (!string.IsNullOrEmpty(seasonResult.ExternalIds?.TvdbId))
{
result.Item.SetProviderId(MetadataProvider.Tvdb, seasonResult.ExternalIds.TvdbId);
diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
index 73eccdf78..54dcee41e 100644
--- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Providers.TV
{
var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType);
- if (item.IndexNumber.HasValue && item.IndexNumber.Value == 0)
+ if (item.IndexNumber == 0 && !item.IsLocked && !item.LockedFields.Contains(MetadataField.Name))
{
var seasonZeroDisplayName = LibraryManager.GetLibraryOptions(item).SeasonZeroDisplayName;
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index bcf9a8366..007101868 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -872,7 +872,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
else
{
// only allow one item of each type
- if (itemResult.RemoteImages.Any(x => x.type == imageType))
+ if (itemResult.RemoteImages.Any(x => x.Type == imageType))
{
return;
}
diff --git a/README.md b/README.md
index 6653bff11..7f6daca68 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,7 @@ These instructions will help you get set up with a local development environment
Before the project can be built, you must first install the [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet) on your system.
-Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET Core development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2017) and [Visual Studio Code](https://code.visualstudio.com/Download).
+Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET 6 development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2022) and [Visual Studio Code](https://code.visualstudio.com/Download).
[ffmpeg](https://github.com/jellyfin/jellyfin-ffmpeg) will also need to be installed.
@@ -138,10 +138,10 @@ A second option is to build the project and then run the resulting executable fi
1. Build the project
- ```bash
- dotnet build # Build the project
- cd bin/Debug/net5.0 # Change into the build output directory
- ```
+```bash
+dotnet build # Build the project
+cd Jellyfin.Server/bin/Debug/net6.0 # Change into the build output directory
+```
2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`.
diff --git a/fedora/Makefile b/fedora/Makefile
index 22cc30448..261fd262d 100644
--- a/fedora/Makefile
+++ b/fedora/Makefile
@@ -3,9 +3,8 @@ 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
+SRPM := jellyfin-$(subst -,~,$(VERSION))-$(RELEASE)$(shell rpm --eval %dist).src.rpm
+TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).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)/
@@ -24,9 +23,9 @@ $(DIR)/$(TARBALL):
cd $(DIR)/; \
SOURCE_DIR=.. \
WORKDIR="$${PWD}"; \
- version=$(GIT_VER); \
+ version=$(VERSION); \
tar \
- --transform "s,^\.,$(NAME)-$(subst -,~,$(GIT_VER))," \
+ --transform "s,^\.,$(NAME)-$(subst -,~,$(VERSION))," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
@@ -43,7 +42,6 @@ $(DIR)/$(TARBALL):
-C $${SOURCE_DIR} ./
$(DIR)/$(SRPM): $(DIR)/$(TARBALL) $(DIR)/jellyfin.spec
- ./bump_version $(GIT_VER)
cd $(DIR)/; \
rpmbuild -bs jellyfin.spec \
--define "_sourcedir $$PWD/" \
diff --git a/jellyfin.ruleset b/jellyfin.ruleset
index e9293588c..dea1a748b 100644
--- a/jellyfin.ruleset
+++ b/jellyfin.ruleset
@@ -11,6 +11,10 @@
<Rule Id="SA1142" Action="Error" />
<!-- error on SA1210: Using directives should be ordered alphabetically by the namespaces -->
<Rule Id="SA1210" Action="Error" />
+ <!-- error on SA1316: Tuple element names should use correct casing -->
+ <Rule Id="SA1316" Action="Error" />
+ <!-- error on SA1414: Tuple types in signatures should have element names -->
+ <Rule Id="SA1414" Action="Error" />
<!-- error on SA1518: File is required to end with a single newline character -->
<Rule Id="SA1518" Action="Error" />
<!-- error on SA1629: Documentation text should end with a period -->
@@ -71,6 +75,8 @@
<Rule Id="CA1843" Action="Error" />
<!-- error on CA1845: Use span-based 'string.Concat' -->
<Rule Id="CA1845" Action="Error" />
+ <!-- error on CA1849: Call async methods when in an async method -->
+ <Rule Id="CA1849" Action="Error" />
<!-- error on CA2016: Forward the CancellationToken parameter to methods that take one
or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token -->
<Rule Id="CA2016" Action="Error" />
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 8476c935e..aaa6b5d90 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -16,7 +16,7 @@
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
- <PackageReference Include="FsCheck.Xunit" Version="2.16.3" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
index 3dcc00ff0..2a3918469 100644
--- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
+++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
@@ -17,7 +17,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
- <PackageReference Include="FsCheck.Xunit" Version="2.16.3" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs b/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs
new file mode 100644
index 000000000..7c3a7ff6c
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Drawing/ImageFormatExtensionsTests.cs
@@ -0,0 +1,33 @@
+using System;
+using System.ComponentModel;
+using MediaBrowser.Model.Drawing;
+using Xunit;
+
+namespace Jellyfin.Model.Drawing;
+
+public static class ImageFormatExtensionsTests
+{
+ private static TheoryData<ImageFormat> GetAllImageFormats()
+ {
+ var theoryTypes = new TheoryData<ImageFormat>();
+ foreach (var x in Enum.GetValues<ImageFormat>())
+ {
+ theoryTypes.Add(x);
+ }
+
+ return theoryTypes;
+ }
+
+ [Theory]
+ [MemberData(nameof(GetAllImageFormats))]
+ public static void GetMimeType_Valid_Valid(ImageFormat format)
+ => Assert.Null(Record.Exception(() => format.GetMimeType()));
+
+ [Theory]
+ [InlineData((ImageFormat)int.MinValue)]
+ [InlineData((ImageFormat)int.MaxValue)]
+ [InlineData((ImageFormat)(-1))]
+ [InlineData((ImageFormat)5)]
+ public static void GetMimeType_Valid_ThrowsInvalidEnumArgumentException(ImageFormat format)
+ => Assert.Throws<InvalidEnumArgumentException>(() => format.GetMimeType());
+}
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index d4a1a30c3..3b6259abd 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -11,7 +11,7 @@
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
- <PackageReference Include="FsCheck.Xunit" Version="2.16.3" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs
index 356ba216d..e81c5152e 100644
--- a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs
@@ -6,6 +6,8 @@ namespace Jellyfin.Naming.Tests.TV
{
public class AbsoluteEpisodeNumberTests
{
+ private readonly EpisodeResolver _resolver = new EpisodeResolver(new NamingOptions());
+
[Theory]
[InlineData("The Simpsons/12.avi", 12)]
[InlineData("The Simpsons/The Simpsons 12.avi", 12)]
@@ -16,10 +18,7 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData("The Simpsons/The Simpsons 101.avi", 101)]
public void GetEpisodeNumberFromFileTest(string path, int episodeNumber)
{
- var options = new NamingOptions();
-
- var result = new EpisodeResolver(options)
- .Resolve(path, false, null, null, true);
+ var result = _resolver.Resolve(path, false, null, null, true);
Assert.Equal(episodeNumber, result?.EpisodeNumber);
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs
index 2937914b9..72052a23c 100644
--- a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs
@@ -6,6 +6,8 @@ namespace Jellyfin.Naming.Tests.TV
{
public class DailyEpisodeTests
{
+ private readonly EpisodeResolver _resolver = new EpisodeResolver(new NamingOptions());
+
[Theory]
[InlineData(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14)]
[InlineData(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14)]
@@ -16,10 +18,7 @@ namespace Jellyfin.Naming.Tests.TV
// TODO: [InlineData(@"/server/Last Man Standing_KTLADT_2018_05_25_01_28_00.wtv", "Last Man Standing", 2018, 05, 25)]
public void Test(string path, string seriesName, int? year, int? month, int? day)
{
- var options = new NamingOptions();
-
- var result = new EpisodeResolver(options)
- .Resolve(path, false);
+ var result = _resolver.Resolve(path, false);
Assert.Null(result?.SeasonNumber);
Assert.Null(result?.EpisodeNumber);
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs
index 8bd1a43d6..1da5a30a8 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs
@@ -6,6 +6,8 @@ namespace Jellyfin.Naming.Tests.TV
{
public class EpisodeNumberWithoutSeasonTests
{
+ private readonly EpisodeResolver _resolver = new EpisodeResolver(new NamingOptions());
+
[Theory]
[InlineData(8, @"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4")]
[InlineData(2, @"The Simpsons/The Simpsons - 02 - Ep Name.avi")]
@@ -24,10 +26,7 @@ namespace Jellyfin.Naming.Tests.TV
// TODO: [InlineData(13, @"Case Closed (1996-2007)/Case Closed - 13.mkv")]
public void GetEpisodeNumberFromFileTest(int episodeNumber, string path)
{
- var options = new NamingOptions();
-
- var result = new EpisodeResolver(options)
- .Resolve(path, false);
+ var result = _resolver.Resolve(path, false);
Assert.Equal(episodeNumber, result?.EpisodeNumber);
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs
index 12fc19bc4..af219b118 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs
@@ -6,6 +6,8 @@ namespace Jellyfin.Naming.Tests.TV
{
public class EpisodePathParserTest
{
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
[Theory]
[InlineData("/media/Foo/Foo-S01E01", true, "Foo", 1, 1)]
[InlineData("/media/Foo - S04E011", true, "Foo", 4, 11)]
@@ -36,8 +38,7 @@ namespace Jellyfin.Naming.Tests.TV
// TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", "The Legend of Condor Heroes 2017", 1, 7)]
public void ParseEpisodesCorrectly(string path, bool isDirectory, string name, int season, int episode)
{
- NamingOptions o = new NamingOptions();
- EpisodePathParser p = new EpisodePathParser(o);
+ EpisodePathParser p = new EpisodePathParser(_namingOptions);
var res = p.Parse(path, isDirectory);
Assert.True(res.Success);
@@ -50,8 +51,7 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData("/test/01-03.avi", true, true)]
public void EpisodePathParserTest_DifferentExpressionsParameters(string path, bool? isNamed, bool? isOptimistic)
{
- NamingOptions o = new NamingOptions();
- EpisodePathParser p = new EpisodePathParser(o);
+ EpisodePathParser p = new EpisodePathParser(_namingOptions);
var res = p.Parse(path, false, isNamed, isOptimistic);
Assert.True(res.Success);
@@ -60,8 +60,7 @@ namespace Jellyfin.Naming.Tests.TV
[Fact]
public void EpisodePathParserTest_FalsePositivePixelRate()
{
- NamingOptions o = new NamingOptions();
- EpisodePathParser p = new EpisodePathParser(o);
+ EpisodePathParser p = new EpisodePathParser(_namingOptions);
var res = p.Parse("Series Special (1920x1080).mkv", false);
Assert.False(res.Success);
@@ -70,14 +69,14 @@ namespace Jellyfin.Naming.Tests.TV
[Fact]
public void EpisodeResolverTest_WrongExtension()
{
- var res = new EpisodeResolver(new NamingOptions()).Resolve("test.mp3", false);
+ var res = new EpisodeResolver(_namingOptions).Resolve("test.mp3", false);
Assert.Null(res);
}
[Fact]
public void EpisodeResolverTest_WrongExtensionStub()
{
- var res = new EpisodeResolver(new NamingOptions()).Resolve("dvd.disc", false);
+ var res = new EpisodeResolver(_namingOptions).Resolve("dvd.disc", false);
Assert.NotNull(res);
Assert.True(res!.IsStub);
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs
deleted file mode 100644
index d0418a49e..000000000
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Emby.Naming.Common;
-using Emby.Naming.TV;
-using Xunit;
-
-namespace Jellyfin.Naming.Tests.TV
-{
- public class EpisodeWithoutSeasonTests
- {
- // TODO: [Theory]
- // TODO: [InlineData(@"/server/anything_ep02.mp4", "anything", null, 2)]
- // TODO: [InlineData(@"/server/anything_ep_02.mp4", "anything", null, 2)]
- // TODO: [InlineData(@"/server/anything_part.II.mp4", "anything", null, null)]
- // TODO: [InlineData(@"/server/anything_pt.II.mp4", "anything", null, null)]
- // TODO: [InlineData(@"/server/anything_pt_II.mp4", "anything", null, null)]
- public void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber)
- {
- var options = new NamingOptions();
-
- var result = new EpisodeResolver(options)
- .Resolve(path, false);
-
- Assert.Equal(seasonNumber, result?.SeasonNumber);
- Assert.Equal(episodeNumber, result?.EpisodeNumber);
- Assert.Equal(seriesName, result?.SeriesName, ignoreCase: true);
- }
- }
-}
diff --git a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
index 58ea0bec5..ffaa64c3f 100644
--- a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
@@ -6,6 +6,8 @@ namespace Jellyfin.Naming.Tests.TV
{
public class MultiEpisodeTests
{
+ private readonly EpisodePathParser _episodePathParser = new EpisodePathParser(new NamingOptions());
+
[Theory]
[InlineData(@"Season 1/4x01 – 20 Hours in America (1).mkv", null)]
[InlineData(@"Season 1/01x02 blah.avi", null)]
@@ -69,10 +71,7 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData(@"Season 1/MOONLIGHTING_s01e01-e04", 4)]
public void TestGetEndingEpisodeNumberFromFile(string filename, int? endingEpisodeNumber)
{
- var options = new NamingOptions();
-
- var result = new EpisodePathParser(options)
- .Parse(filename, false);
+ var result = _episodePathParser.Parse(filename, false);
Assert.Equal(result.EndingEpisodeNumber, endingEpisodeNumber);
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs
index 4837e3a3b..58ec1b5d2 100644
--- a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs
@@ -6,7 +6,7 @@ namespace Jellyfin.Naming.Tests.TV
{
public class SeasonNumberTests
{
- private readonly NamingOptions _namingOptions = new NamingOptions();
+ private readonly EpisodeResolver _resolver = new EpisodeResolver(new NamingOptions());
[Theory]
[InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 25)]
@@ -56,8 +56,7 @@ namespace Jellyfin.Naming.Tests.TV
// TODO: [InlineData(@"Seinfeld/Seinfeld 0807 The Checks.avi", 8)]
public void GetSeasonNumberFromEpisodeFileTest(string path, int? expected)
{
- var result = new EpisodeResolver(_namingOptions)
- .Resolve(path, false);
+ var result = _resolver.Resolve(path, false);
Assert.Equal(expected, result?.SeasonNumber);
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs
index ceb5f8b73..e6b0409db 100644
--- a/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs
@@ -6,6 +6,8 @@ namespace Jellyfin.Naming.Tests.TV
{
public class SeriesPathParserTest
{
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
[Theory]
[InlineData("The.Show.S01", "The.Show")]
[InlineData("/The.Show.S01", "The.Show")]
@@ -18,8 +20,7 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData("/something/The Show/S01", "The Show")]
public void SeriesPathParserParseTest(string path, string name)
{
- NamingOptions o = new NamingOptions();
- var res = SeriesPathParser.Parse(o, path);
+ var res = SeriesPathParser.Parse(_namingOptions, path);
Assert.Equal(name, res.SeriesName);
Assert.True(res.Success);
diff --git a/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs
index 97f4b4058..84758c9c3 100644
--- a/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs
@@ -6,6 +6,8 @@ namespace Jellyfin.Naming.Tests.TV
{
public class SeriesResolverTests
{
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
[Theory]
[InlineData("The.Show.S01", "The Show")]
[InlineData("The.Show.S01.COMPLETE", "The Show")]
@@ -19,8 +21,7 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData("/some/path/The Show s02e10 the episode 720p hdtv", "The Show")]
public void SeriesResolverResolveTest(string path, string name)
{
- NamingOptions o = new NamingOptions();
- var res = SeriesResolver.Resolve(o, path);
+ var res = SeriesResolver.Resolve(_namingOptions, path);
Assert.Equal(name, res.Name);
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
index 6d49ac832..fa46ecc3a 100644
--- a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
@@ -7,6 +7,8 @@ namespace Jellyfin.Naming.Tests.TV
{
public class SimpleEpisodeTests
{
+ private readonly EpisodeResolver _resolver = new EpisodeResolver(new NamingOptions());
+
[Theory]
[InlineData("/server/anything_s01e02.mp4", "anything", 1, 2)]
[InlineData("/server/anything_s1e2.mp4", "anything", 1, 2)]
@@ -23,39 +25,25 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData(@"Love.Death.and.Robots.S01.1080p.NF.WEB-DL.DDP5.1.x264-NTG/Love.Death.and.Robots.S01E01.Sonnies.Edge.1080p.NF.WEB-DL.DDP5.1.x264-NTG.mkv", "Love.Death.and.Robots", 1, 1)]
[InlineData("[YuiSubs] Tensura Nikki - Tensei Shitara Slime Datta Ken/[YuiSubs] Tensura Nikki - Tensei Shitara Slime Datta Ken - 12 (NVENC H.265 1080p).mkv", "Tensura Nikki - Tensei Shitara Slime Datta Ken", null, 12)]
[InlineData("[Baz-Bar]Foo - 01 - 12[1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)]
+ [InlineData("Series/4-12 - The Woman.mp4", "", 4, 12, 12)]
// TODO: [InlineData("E:\\Anime\\Yahari Ore no Seishun Love Comedy wa Machigatteiru\\Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku\\Oregairu Zoku 11 - Hayama Hayato Always Renconds to Everyone's Expectations..mkv", "Yahari Ore no Seishun Love Comedy wa Machigatteiru", null, 11)]
// TODO: [InlineData(@"/Library/Series/The Grand Tour (2016)/Season 1/S01E01 The Holy Trinity.mkv", "The Grand Tour", 1, 1)]
- public void TestSimple(string path, string seriesName, int? seasonNumber, int? episodeNumber)
+ public void TestSimple(string path, string seriesName, int? seasonNumber, int? episodeNumber, int? episodeEndNumber = null)
{
- Test(path, seriesName, seasonNumber, episodeNumber, null);
- }
-
- [Theory]
- [InlineData("Series/4-12 - The Woman.mp4", "", 4, 12, 12)]
- public void TestWithPossibleEpisodeEnd(string path, string seriesName, int? seasonNumber, int? episodeNumber, int? episodeEndNumber)
- {
- Test(path, seriesName, seasonNumber, episodeNumber, episodeEndNumber);
- }
-
- private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber, int? episodeEndNumber)
- {
- var options = new NamingOptions();
-
- var result = new EpisodeResolver(options)
- .Resolve(path, false);
+ var result = _resolver.Resolve(path, false);
Assert.NotNull(result);
- Assert.Equal(seasonNumber, result?.SeasonNumber);
- Assert.Equal(episodeNumber, result?.EpisodeNumber);
- Assert.Equal(seriesName, result?.SeriesName, true);
- Assert.Equal(path, result?.Path);
+ Assert.Equal(seasonNumber, result!.SeasonNumber);
+ Assert.Equal(episodeNumber, result!.EpisodeNumber);
+ Assert.Equal(seriesName, result!.SeriesName, true);
+ Assert.Equal(path, result!.Path);
Assert.Equal(Path.GetExtension(path).Substring(1), result?.Container);
- Assert.Null(result?.Format3D);
- Assert.False(result?.Is3D);
- Assert.False(result?.IsStub);
- Assert.Null(result?.StubType);
- Assert.Equal(episodeEndNumber, result?.EndingEpisodeNumber);
- Assert.False(result?.IsByDate);
+ Assert.Null(result!.Format3D);
+ Assert.False(result!.Is3D);
+ Assert.False(result!.IsStub);
+ Assert.Null(result!.StubType);
+ Assert.Equal(episodeEndNumber, result!.EndingEpisodeNumber);
+ Assert.False(result!.IsByDate);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
index 24f9bf98d..731580e0c 100644
--- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
@@ -82,7 +82,7 @@ namespace Jellyfin.Naming.Tests.Video
private void Test(string input, ExtraType? expectedType)
{
- var extraType = ExtraResolver.GetExtraInfo(input, _videoOptions).ExtraType;
+ var extraType = ExtraRuleResolver.GetExtraInfo(input, _videoOptions).ExtraType;
Assert.Equal(expectedType, extraType);
}
@@ -92,7 +92,7 @@ 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 = ExtraResolver.GetExtraInfo("extra.mp4", options);
+ var res = ExtraRuleResolver.GetExtraInfo("extra.mp4", options);
Assert.Equal(rule, res.Rule);
}
diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index 87acc7f68..7f9b60b9e 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -16,7 +16,7 @@
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
- <PackageReference Include="FsCheck.Xunit" Version="2.16.3" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs
index 1ce2096ea..ef8f7cd90 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs
@@ -13,7 +13,7 @@ namespace Jellyfin.Server.Implementations.Tests.HttpServer
[Fact]
public void DeserializeWebSocketMessage_SingleSegment_Success()
{
- var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!);
var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json");
con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed);
Assert.Equal(109, bytesConsumed);
@@ -23,7 +23,7 @@ namespace Jellyfin.Server.Implementations.Tests.HttpServer
public void DeserializeWebSocketMessage_MultipleSegments_Success()
{
const int SplitPos = 64;
- var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!);
var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json");
var seg1 = new BufferSegment(new Memory<byte>(bytes, 0, SplitPos));
var seg2 = seg1.Append(new Memory<byte>(bytes, SplitPos, bytes.Length - SplitPos));
@@ -34,7 +34,7 @@ namespace Jellyfin.Server.Implementations.Tests.HttpServer
[Fact]
public void DeserializeWebSocketMessage_ValidPartial_Success()
{
- var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!);
var bytes = File.ReadAllBytes("Test Data/HttpServer/ValidPartial.json");
con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed);
Assert.Equal(109, bytesConsumed);
@@ -43,7 +43,7 @@ namespace Jellyfin.Server.Implementations.Tests.HttpServer
[Fact]
public void DeserializeWebSocketMessage_Partial_ThrowJsonException()
{
- var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!);
var bytes = File.ReadAllBytes("Test Data/HttpServer/Partial.json");
Assert.Throws<JsonException>(() => con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed));
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs
index 3ce29f28c..f5c8cc970 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs
@@ -1,16 +1,18 @@
-using System.Collections.Generic;
+using System;
+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 Emby.Server.Implementations.Library.Resolvers.Audio;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting;
@@ -32,11 +34,13 @@ public class FindExtrasTests
fixture.Register(() => new NamingOptions());
var configMock = fixture.Freeze<Mock<IServerConfigurationManager>>();
configMock.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
+ var itemRepository = fixture.Freeze<Mock<IItemRepository>>();
+ itemRepository.Setup(i => i.RetrieveItem(It.IsAny<Guid>())).Returns<BaseItem>(null);
_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 VideoExtraResolver(fixture.Create<NamingOptions>()), new AudioResolver(fixture.Create<NamingOptions>()) },
+ new List<IItemResolver> { new AudioResolver(fixture.Create<NamingOptions>()) },
fixture.Create<IEnumerable<IIntroProvider>>(),
fixture.Create<IEnumerable<IBaseItemComparer>>(),
fixture.Create<IEnumerable<ILibraryPostScanTask>>()))
@@ -56,7 +60,8 @@ public class FindExtrasTests
"/movies/Up/Up.mkv",
"/movies/Up/Up - trailer.mkv",
"/movies/Up/Up - sample.mkv",
- "/movies/Up/Up something else.mkv"
+ "/movies/Up/Up something else.mkv",
+ "/movies/Up/Up-extra.mkv"
};
var files = paths.Select(p => new FileSystemMetadata
@@ -67,10 +72,11 @@ public class FindExtrasTests
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);
- Assert.Equal(typeof(Trailer), extras[0].GetType());
- Assert.Equal(ExtraType.Sample, extras[1].ExtraType);
+ Assert.Equal(3, extras.Count);
+ Assert.Equal(ExtraType.Unknown, extras[0].ExtraType);
+ Assert.Equal(ExtraType.Trailer, extras[1].ExtraType);
+ Assert.Equal(typeof(Trailer), extras[1].GetType());
+ Assert.Equal(ExtraType.Sample, extras[2].ExtraType);
}
[Fact]
@@ -88,7 +94,8 @@ public class FindExtrasTests
"/movies/Up/behind the scenes",
"/movies/Up/behind the scenes.mkv",
"/movies/Up/Up - sample.mkv",
- "/movies/Up/Up something else.mkv"
+ "/movies/Up/Up something else.mkv",
+ "/movies/Up/extras"
};
_fileSystemMock.Setup(f => f.GetFiles(
@@ -104,7 +111,7 @@ public class FindExtrasTests
Name = "some trailer.mkv",
IsDirectory = false
}
- });
+ }).Verifiable();
_fileSystemMock.Setup(f => f.GetFiles(
"/movies/Up/behind the scenes",
@@ -119,7 +126,7 @@ public class FindExtrasTests
Name = "the making of Up.mkv",
IsDirectory = false
}
- });
+ }).Verifiable();
_fileSystemMock.Setup(f => f.GetFiles(
"/movies/Up/theme-music",
@@ -134,26 +141,46 @@ public class FindExtrasTests
Name = "theme2.mp3",
IsDirectory = false
}
- });
+ }).Verifiable();
+
+ _fileSystemMock.Setup(f => f.GetFiles(
+ "/movies/Up/extras",
+ It.IsAny<string[]>(),
+ false,
+ false))
+ .Returns(new List<FileSystemMetadata>
+ {
+ new()
+ {
+ FullName = "/movies/Up/extras/Honest Trailer.mkv",
+ Name = "Honest Trailer.mkv",
+ IsDirectory = false
+ }
+ }).Verifiable();
var files = paths.Select(p => new FileSystemMetadata
{
FullName = p,
Name = Path.GetFileName(p),
- IsDirectory = string.IsNullOrEmpty(Path.GetExtension(p))
+ IsDirectory = !Path.HasExtension(p)
}).ToList();
var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList();
- Assert.Equal(6, extras.Count);
- Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
- Assert.Equal(typeof(Trailer), extras[0].GetType());
+ _fileSystemMock.Verify();
+ Assert.Equal(7, extras.Count);
+ Assert.Equal(ExtraType.Unknown, extras[0].ExtraType);
+ Assert.Equal(typeof(Video), extras[0].GetType());
Assert.Equal(ExtraType.Trailer, extras[1].ExtraType);
Assert.Equal(typeof(Trailer), extras[1].GetType());
- Assert.Equal(ExtraType.BehindTheScenes, extras[2].ExtraType);
- Assert.Equal(ExtraType.Sample, extras[3].ExtraType);
- Assert.Equal(ExtraType.ThemeSong, extras[4].ExtraType);
+ Assert.Equal(ExtraType.Trailer, extras[2].ExtraType);
+ Assert.Equal(typeof(Trailer), extras[2].GetType());
+ Assert.Equal(ExtraType.BehindTheScenes, extras[3].ExtraType);
+ Assert.Equal(ExtraType.Sample, extras[4].ExtraType);
Assert.Equal(ExtraType.ThemeSong, extras[5].ExtraType);
+ Assert.Equal(typeof(Audio), extras[5].GetType());
+ Assert.Equal(ExtraType.ThemeSong, extras[6].ExtraType);
+ Assert.Equal(typeof(Audio), extras[6].GetType());
}
[Fact]
@@ -210,6 +237,46 @@ public class FindExtrasTests
}
[Fact]
+ public void FindExtras_WrongExtensions_FindsNoExtras()
+ {
+ var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" };
+ var paths = new List<string>
+ {
+ "/movies/Up/Up.mkv",
+ "/movies/Up/trailer.noext",
+ "/movies/Up/theme.png",
+ "/movies/Up/trailers"
+ };
+
+ var files = paths.Select(p => new FileSystemMetadata
+ {
+ FullName = p,
+ Name = Path.GetFileName(p),
+ IsDirectory = !Path.HasExtension(p)
+ }).ToList();
+
+ _fileSystemMock.Setup(f => f.GetFiles(
+ "/movies/Up/trailers",
+ It.IsAny<string[]>(),
+ false,
+ false))
+ .Returns(new List<FileSystemMetadata>
+ {
+ new()
+ {
+ FullName = "/movies/Up/trailers/trailer.jpg",
+ Name = "trailer.jpg",
+ IsDirectory = false
+ }
+ }).Verifiable();
+
+ var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList();
+
+ _fileSystemMock.Verify();
+ Assert.Empty(extras);
+ }
+
+ [Fact]
public void FindExtras_SeriesWithTrailers_FindsCorrectExtras()
{
var owner = new Series { Name = "Dexter", Path = "/series/Dexter" };
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
index 54a63a5f2..be2dfe0a8 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
@@ -8,13 +8,17 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
[Theory]
[InlineData("Superman: Red Son [imdbid=tt10985510]", "imdbid", "tt10985510")]
+ [InlineData("Superman: Red Son [imdbid-tt10985510]", "imdbid", "tt10985510")]
[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 [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("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")]
[InlineData("tmdbid=111111][tmdbid=618355]", "tmdbid", "618355")]
[InlineData("[tmdbid=618355]tmdbid=111111]", "tmdbid", "618355")]
[InlineData("tmdbid=618355]", "tmdbid", null)]
@@ -23,6 +27,8 @@ namespace Jellyfin.Server.Implementations.Tests.Library
[InlineData("tmdbid=", "tmdbid", null)]
[InlineData("tmdbid", "tmdbid", null)]
[InlineData("[tmdbid=][imdbid=tt10985510]", "tmdbid", null)]
+ [InlineData("[tmdbid-][imdbid-tt10985510]", "tmdbid", null)]
+ [InlineData("Superman: Red Son [tmdbid-618355][tmdbid=1234567]", "tmdbid", "618355")]
public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult)
{
Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute));
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index 7ea45d14d..7c9952030 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Jellyfin.Data.Entities;
@@ -157,33 +158,33 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
// Images
Assert.Equal(7, result.RemoteImages.Count);
- var posters = result.RemoteImages.Where(x => x.type == ImageType.Primary).ToList();
+ var posters = result.RemoteImages.Where(x => x.Type == ImageType.Primary).ToList();
Assert.Single(posters);
- Assert.Equal("http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", posters[0].url);
+ Assert.Equal("http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", posters[0].Url);
- var logos = result.RemoteImages.Where(x => x.type == ImageType.Logo).ToList();
+ var logos = result.RemoteImages.Where(x => x.Type == ImageType.Logo).ToList();
Assert.Single(logos);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png", logos[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png", logos[0].Url);
- var banners = result.RemoteImages.Where(x => x.type == ImageType.Banner).ToList();
+ var banners = result.RemoteImages.Where(x => x.Type == ImageType.Banner).ToList();
Assert.Single(banners);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg", banners[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg", banners[0].Url);
- var thumbs = result.RemoteImages.Where(x => x.type == ImageType.Thumb).ToList();
+ var thumbs = result.RemoteImages.Where(x => x.Type == ImageType.Thumb).ToList();
Assert.Single(thumbs);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg", thumbs[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg", thumbs[0].Url);
- var art = result.RemoteImages.Where(x => x.type == ImageType.Art).ToList();
+ var art = result.RemoteImages.Where(x => x.Type == ImageType.Art).ToList();
Assert.Single(art);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png", art[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png", art[0].Url);
- var discArt = result.RemoteImages.Where(x => x.type == ImageType.Disc).ToList();
+ var discArt = result.RemoteImages.Where(x => x.Type == ImageType.Disc).ToList();
Assert.Single(discArt);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].Url);
- var backdrop = result.RemoteImages.Where(x => x.type == ImageType.Backdrop).ToList();
+ var backdrop = result.RemoteImages.Where(x => x.Type == ImageType.Backdrop).ToList();
Assert.Single(backdrop);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg", backdrop[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg", backdrop[0].Url);
// Local Image - contains only one item depending on operating system
Assert.Single(result.Images);
@@ -216,8 +217,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
_parser.Fetch(result, "Test Data/Fanart.nfo", CancellationToken.None);
- Assert.Single(result.RemoteImages.Where(x => x.type == ImageType.Backdrop));
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg", result.RemoteImages.First(x => x.type == ImageType.Backdrop).url);
+ Assert.Single(result.RemoteImages.Where(x => x.Type == ImageType.Backdrop));
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg", result.RemoteImages.First(x => x.Type == ImageType.Backdrop).Url);
}
[Fact]