diff options
27 files changed, 184 insertions, 128 deletions
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/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.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 464a621cf..bd0c178fd 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -3007,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/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index f1a6ef344..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) { @@ -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(); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 9ed0d8d73..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) - { - await fileStream.WriteAsync(buffer.AsMemory(RtpHeaderBytes, read), linkedSource.Token).ConfigureAwait(false); - } - - 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/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/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 5e299ea0e..e32ab4ca8 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -119,5 +119,6 @@ "Undefined": "לא מוגדר", "Forced": "כפוי", "Default": "ברירת מחדל", - "TaskOptimizeDatabase": "מיטוב מסד נתונים" + "TaskOptimizeDatabase": "מיטוב מסד נתונים", + "TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים." } 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/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/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/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 1471f5a24..2cfd36d00 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -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/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..b9455721e 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -26,6 +26,7 @@ namespace MediaBrowser.Controller.Entities.Movies .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 7cdd07239..c8a0e21eb 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -22,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(); 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/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9c96d2472..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; @@ -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('"'); } @@ -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; } @@ -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.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/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index ee8451853..3b03466e9 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -116,7 +116,6 @@ namespace MediaBrowser.Model.Net { "audio/x-wavpack", ".wv" }, // Type image - { "image/jpg", ".jpg" }, { "image/jpeg", ".jpg" }, { "image/x-png", ".png" }, diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index b1d73c4c4..0d1bdec58 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; @@ -172,7 +173,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) { @@ -195,12 +202,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/jellyfin.ruleset b/jellyfin.ruleset index 52bedabee..dea1a748b 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -13,6 +13,8 @@ <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 --> @@ -73,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.Model.Tests/Net/MimeTypesTests.cs b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs index 7b50c54b0..cbab455f0 100644 --- a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs +++ b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs @@ -124,7 +124,6 @@ namespace Jellyfin.Model.Tests.Net [InlineData("font/woff2", ".woff2")] [InlineData("image/bmp", ".bmp")] [InlineData("image/gif", ".gif")] - [InlineData("image/jpg", ".jpg")] [InlineData("image/jpeg", ".jpg")] [InlineData("image/png", ".png")] [InlineData("image/svg+xml", ".svg")] 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> |
