diff options
Diffstat (limited to 'Jellyfin.Api/Helpers')
| -rw-r--r-- | Jellyfin.Api/Helpers/ClaimHelpers.cs | 13 | ||||
| -rw-r--r-- | Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 2 | ||||
| -rw-r--r-- | Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 5 | ||||
| -rw-r--r-- | Jellyfin.Api/Helpers/MediaInfoHelper.cs | 4 | ||||
| -rw-r--r-- | Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 166 | ||||
| -rw-r--r-- | Jellyfin.Api/Helpers/RequestHelpers.cs | 69 | ||||
| -rw-r--r-- | Jellyfin.Api/Helpers/SimilarItemsHelper.cs | 4 |
7 files changed, 253 insertions, 10 deletions
diff --git a/Jellyfin.Api/Helpers/ClaimHelpers.cs b/Jellyfin.Api/Helpers/ClaimHelpers.cs index df235ced2..29e6b4193 100644 --- a/Jellyfin.Api/Helpers/ClaimHelpers.cs +++ b/Jellyfin.Api/Helpers/ClaimHelpers.cs @@ -63,6 +63,19 @@ namespace Jellyfin.Api.Helpers public static string? GetToken(in ClaimsPrincipal user) => GetClaimValue(user, InternalClaimTypes.Token); + /// <summary> + /// Gets a flag specifying whether the request is using an api key. + /// </summary> + /// <param name="user">Current claims principal.</param> + /// <returns>The flag specifying whether the request is using an api key.</returns> + public static bool GetIsApiKey(in ClaimsPrincipal user) + { + var claimValue = GetClaimValue(user, InternalClaimTypes.IsApiKey); + return !string.IsNullOrEmpty(claimValue) + && bool.TryParse(claimValue, out var parsedClaimValue) + && parsedClaimValue; + } + private static string? GetClaimValue(in ClaimsPrincipal user, string name) { return user?.Identities diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index af0519ffa..ea012f837 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -155,7 +155,7 @@ namespace Jellyfin.Api.Helpers return new FileContentResult(Array.Empty<byte>(), MimeTypes.GetMimeType("playlist.m3u8")); } - var totalBitrate = state.OutputAudioBitrate ?? 0 + state.OutputVideoBitrate ?? 0; + var totalBitrate = (state.OutputAudioBitrate ?? 0) + (state.OutputVideoBitrate ?? 0); var builder = new StringBuilder(); diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 6b516977e..366301d3e 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -123,9 +123,8 @@ namespace Jellyfin.Api.Helpers state.Dispose(); } - await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None) - .WriteToAsync(httpContext.Response.Body, CancellationToken.None).ConfigureAwait(false); - return new FileStreamResult(httpContext.Response.Body, contentType); + var stream = new ProgressiveFileStream(outputPath, job, transcodingJobHelper); + return new FileStreamResult(stream, contentType); } finally { diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index e78f63b25..0d8315dee 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -166,7 +166,7 @@ namespace Jellyfin.Api.Helpers MediaSourceInfo mediaSource, DeviceProfile profile, AuthorizationInfo auth, - long? maxBitrate, + int? maxBitrate, long startTimeTicks, string mediaSourceId, int? audioStreamIndex, @@ -551,7 +551,7 @@ namespace Jellyfin.Api.Helpers } } - private long? GetMaxBitrate(long? clientMaxBitrate, User user, string ipAddress) + private int? GetMaxBitrate(int? clientMaxBitrate, User user, string ipAddress) { var maxBitrate = clientMaxBitrate; var remoteClientMaxBitrate = user.RemoteClientBitrateLimit ?? 0; diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs new file mode 100644 index 000000000..824870c7e --- /dev/null +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -0,0 +1,166 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Models.PlaybackDtos; +using MediaBrowser.Model.IO; + +namespace Jellyfin.Api.Helpers +{ + /// <summary> + /// A progressive file stream for transferring transcoded files as they are written to. + /// </summary> + public class ProgressiveFileStream : Stream + { + private readonly FileStream _fileStream; + private readonly TranscodingJobDto? _job; + private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly bool _allowAsyncFileRead; + private int _bytesWritten; + private bool _disposed; + + /// <summary> + /// Initializes a new instance of the <see cref="ProgressiveFileStream"/> class. + /// </summary> + /// <param name="filePath">The path to the transcoded file.</param> + /// <param name="job">The transcoding job information.</param> + /// <param name="transcodingJobHelper">The transcoding job helper.</param> + public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper) + { + _job = job; + _transcodingJobHelper = transcodingJobHelper; + _bytesWritten = 0; + + var fileOptions = FileOptions.SequentialScan; + _allowAsyncFileRead = false; + + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + fileOptions |= FileOptions.Asynchronous; + _allowAsyncFileRead = true; + } + + _fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions); + } + + /// <inheritdoc /> + public override bool CanRead => _fileStream.CanRead; + + /// <inheritdoc /> + public override bool CanSeek => false; + + /// <inheritdoc /> + public override bool CanWrite => false; + + /// <inheritdoc /> + public override long Length => throw new NotSupportedException(); + + /// <inheritdoc /> + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + /// <inheritdoc /> + public override void Flush() + { + _fileStream.Flush(); + } + + /// <inheritdoc /> + public override int Read(byte[] buffer, int offset, int count) + { + return _fileStream.Read(buffer, offset, count); + } + + /// <inheritdoc /> + public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + int totalBytesRead = 0; + int remainingBytesToRead = count; + + int newOffset = offset; + while (remainingBytesToRead > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + int bytesRead; + if (_allowAsyncFileRead) + { + bytesRead = await _fileStream.ReadAsync(buffer, newOffset, remainingBytesToRead, cancellationToken).ConfigureAwait(false); + } + else + { + bytesRead = _fileStream.Read(buffer, newOffset, remainingBytesToRead); + } + + remainingBytesToRead -= bytesRead; + newOffset += bytesRead; + + if (bytesRead > 0) + { + _bytesWritten += bytesRead; + totalBytesRead += bytesRead; + + if (_job != null) + { + _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten); + } + } + else + { + // If the job is null it's a live stream and will require user action to close + if (_job?.HasExited ?? false) + { + break; + } + + await Task.Delay(50, cancellationToken).ConfigureAwait(false); + } + } + + return totalBytesRead; + } + + /// <inheritdoc /> + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); + + /// <inheritdoc /> + public override void SetLength(long value) + => throw new NotSupportedException(); + + /// <inheritdoc /> + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + /// <inheritdoc /> + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + try + { + if (disposing) + { + _fileStream.Dispose(); + + if (_job != null) + { + _transcodingJobHelper.OnTranscodeEndRequest(_job); + } + } + } + finally + { + _disposed = true; + base.Dispose(disposing); + } + } + } +} diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index fb5c283f5..49632dd01 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -1,11 +1,14 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Net; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Http; @@ -161,5 +164,67 @@ namespace Jellyfin.Api.Helpers .Select(i => i!.Value) .ToArray(); } + + /// <summary> + /// Gets the item fields. + /// </summary> + /// <param name="imageTypes">The image types string.</param> + /// <returns>IEnumerable{ItemFields}.</returns> + internal static ImageType[] GetImageTypes(string? imageTypes) + { + if (string.IsNullOrEmpty(imageTypes)) + { + return Array.Empty<ImageType>(); + } + + return Split(imageTypes, ',', true) + .Select(v => + { + if (Enum.TryParse(v, true, out ImageType value)) + { + return (ImageType?)value; + } + + return null; + }) + .Where(i => i.HasValue) + .Select(i => i!.Value) + .ToArray(); + } + + internal static QueryResult<BaseItemDto> CreateQueryResult( + QueryResult<(BaseItem, ItemCounts)> result, + DtoOptions dtoOptions, + IDtoService dtoService, + bool includeItemTypes, + User? user) + { + var dtos = result.Items.Select(i => + { + var (baseItem, counts) = i; + var dto = dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user); + + if (includeItemTypes) + { + dto.ChildCount = counts.ItemCount; + dto.ProgramCount = counts.ProgramCount; + dto.SeriesCount = counts.SeriesCount; + dto.EpisodeCount = counts.EpisodeCount; + dto.MovieCount = counts.MovieCount; + dto.TrailerCount = counts.TrailerCount; + dto.AlbumCount = counts.AlbumCount; + dto.SongCount = counts.SongCount; + dto.ArtistCount = counts.ArtistCount; + } + + return dto; + }); + + return new QueryResult<BaseItemDto> + { + Items = dtos.ToArray(), + TotalRecordCount = result.TotalRecordCount + }; + } } } diff --git a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs b/Jellyfin.Api/Helpers/SimilarItemsHelper.cs index b922e76cf..6b06f87cd 100644 --- a/Jellyfin.Api/Helpers/SimilarItemsHelper.cs +++ b/Jellyfin.Api/Helpers/SimilarItemsHelper.cs @@ -50,9 +50,9 @@ namespace Jellyfin.Api.Helpers var returnItems = items; - if (limit.HasValue) + if (limit.HasValue && limit < returnItems.Count) { - returnItems = returnItems.Take(limit.Value).ToList(); + returnItems = returnItems.GetRange(0, limit.Value); } var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user); |
