aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Controllers')
-rw-r--r--Jellyfin.Api/Controllers/ApiKeyController.cs6
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs18
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs173
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs231
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs10
-rw-r--r--Jellyfin.Api/Controllers/ItemLookupController.cs3
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs17
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs18
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs19
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs9
-rw-r--r--Jellyfin.Api/Controllers/SuggestionsController.cs9
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs33
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs19
-rw-r--r--Jellyfin.Api/Controllers/UserViewsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs7
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs8
17 files changed, 295 insertions, 297 deletions
diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs
index 8e0332d3e..593846adc 100644
--- a/Jellyfin.Api/Controllers/ApiKeyController.cs
+++ b/Jellyfin.Api/Controllers/ApiKeyController.cs
@@ -38,11 +38,7 @@ namespace Jellyfin.Api.Controllers
{
var keys = await _authenticationManager.GetApiKeys();
- return new QueryResult<AuthenticationInfo>
- {
- Items = keys,
- TotalRecordCount = keys.Count
- };
+ return new QueryResult<AuthenticationInfo>(keys);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 3df975563..b54825775 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -243,11 +243,10 @@ namespace Jellyfin.Api.Controllers
return dto;
});
- return new QueryResult<BaseItemDto>
- {
- Items = dtos.ToArray(),
- TotalRecordCount = result.TotalRecordCount
- };
+ return new QueryResult<BaseItemDto>(
+ query.StartIndex,
+ result.TotalRecordCount,
+ dtos.ToArray());
}
/// <summary>
@@ -447,11 +446,10 @@ namespace Jellyfin.Api.Controllers
return dto;
});
- return new QueryResult<BaseItemDto>
- {
- Items = dtos.ToArray(),
- TotalRecordCount = result.TotalRecordCount
- };
+ return new QueryResult<BaseItemDto>(
+ query.StartIndex,
+ result.TotalRecordCount,
+ dtos.ToArray());
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 6ef3a2ff9..f2fdeeea5 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -13,6 +13,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
+using Jellyfin.MediaEncoding.Hls.Playlist;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
@@ -28,7 +29,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
namespace Jellyfin.Api.Controllers
{
@@ -55,6 +55,7 @@ namespace Jellyfin.Api.Controllers
private readonly TranscodingJobHelper _transcodingJobHelper;
private readonly ILogger<DynamicHlsController> _logger;
private readonly EncodingHelper _encodingHelper;
+ private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator;
private readonly DynamicHlsHelper _dynamicHlsHelper;
private readonly EncodingOptions _encodingOptions;
@@ -74,6 +75,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param>
/// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
+ /// <param name="dynamicHlsPlaylistGenerator">Instance of <see cref="IDynamicHlsPlaylistGenerator"/>.</param>
public DynamicHlsController(
ILibraryManager libraryManager,
IUserManager userManager,
@@ -87,7 +89,8 @@ namespace Jellyfin.Api.Controllers
TranscodingJobHelper transcodingJobHelper,
ILogger<DynamicHlsController> logger,
DynamicHlsHelper dynamicHlsHelper,
- EncodingHelper encodingHelper)
+ EncodingHelper encodingHelper,
+ IDynamicHlsPlaylistGenerator dynamicHlsPlaylistGenerator)
{
_libraryManager = libraryManager;
_userManager = userManager;
@@ -102,6 +105,7 @@ namespace Jellyfin.Api.Controllers
_logger = logger;
_dynamicHlsHelper = dynamicHlsHelper;
_encodingHelper = encodingHelper;
+ _dynamicHlsPlaylistGenerator = dynamicHlsPlaylistGenerator;
_encodingOptions = serverConfigurationManager.GetEncodingOptions();
}
@@ -385,6 +389,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
+ /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
+ /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
@@ -441,6 +447,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] long? startTimeTicks,
[FromQuery] int? width,
[FromQuery] int? height,
+ [FromQuery] int? maxWidth,
+ [FromQuery] int? maxHeight,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod? subtitleMethod,
@@ -493,6 +501,8 @@ namespace Jellyfin.Api.Controllers
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
+ MaxWidth = maxWidth,
+ MaxHeight = maxHeight,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
@@ -717,6 +727,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
+ /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
+ /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
@@ -771,6 +783,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] long? startTimeTicks,
[FromQuery] int? width,
[FromQuery] int? height,
+ [FromQuery] int? maxWidth,
+ [FromQuery] int? maxHeight,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod? subtitleMethod,
@@ -823,6 +837,8 @@ namespace Jellyfin.Api.Controllers
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
+ MaxWidth = maxWidth,
+ MaxHeight = maxHeight,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
@@ -844,7 +860,7 @@ namespace Jellyfin.Api.Controllers
StreamOptions = streamOptions
};
- return await GetVariantPlaylistInternal(streamingRequest, "main", cancellationTokenSource)
+ return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
.ConfigureAwait(false);
}
@@ -1009,7 +1025,7 @@ namespace Jellyfin.Api.Controllers
StreamOptions = streamOptions
};
- return await GetVariantPlaylistInternal(streamingRequest, "main", cancellationTokenSource)
+ return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
.ConfigureAwait(false);
}
@@ -1020,13 +1036,15 @@ namespace Jellyfin.Api.Controllers
/// <param name="playlistId">The playlist id.</param>
/// <param name="segmentId">The segment id.</param>
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
+ /// <param name="runtimeTicks">The position of the requested segment in ticks.</param>
+ /// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
- /// <param name="segmentLength">The segment lenght.</param>
+ /// <param name="segmentLength">The desired segment length.</param>
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
@@ -1048,6 +1066,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
+ /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
+ /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
@@ -1078,6 +1098,8 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string playlistId,
[FromRoute, Required] int segmentId,
[FromRoute, Required] string container,
+ [FromQuery, Required] long runtimeTicks,
+ [FromQuery, Required] long actualSegmentLengthTicks,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@@ -1106,6 +1128,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] long? startTimeTicks,
[FromQuery] int? width,
[FromQuery] int? height,
+ [FromQuery] int? maxWidth,
+ [FromQuery] int? maxHeight,
[FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod? subtitleMethod,
@@ -1129,6 +1153,8 @@ namespace Jellyfin.Api.Controllers
var streamingRequest = new VideoRequestDto
{
Id = itemId,
+ CurrentRuntimeTicks = runtimeTicks,
+ ActualSegmentLengthTicks = actualSegmentLengthTicks,
Container = container,
Static = @static ?? false,
Params = @params,
@@ -1158,6 +1184,8 @@ namespace Jellyfin.Api.Controllers
StartTimeTicks = startTimeTicks,
Width = width,
Height = height,
+ MaxWidth = maxWidth,
+ MaxHeight = maxHeight,
VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
@@ -1190,6 +1218,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="playlistId">The playlist id.</param>
/// <param name="segmentId">The segment id.</param>
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
+ /// <param name="runtimeTicks">The position of the requested segment in ticks.</param>
+ /// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
@@ -1249,6 +1279,8 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string playlistId,
[FromRoute, Required] int segmentId,
[FromRoute, Required] string container,
+ [FromQuery, Required] long runtimeTicks,
+ [FromQuery, Required] long actualSegmentLengthTicks,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@@ -1302,6 +1334,8 @@ namespace Jellyfin.Api.Controllers
{
Id = itemId,
Container = container,
+ CurrentRuntimeTicks = runtimeTicks,
+ ActualSegmentLengthTicks = actualSegmentLengthTicks,
Static = @static ?? false,
Params = @params,
Tag = tag,
@@ -1355,7 +1389,7 @@ namespace Jellyfin.Api.Controllers
.ConfigureAwait(false);
}
- private async Task<ActionResult> GetVariantPlaylistInternal(StreamingRequestDto streamingRequest, string name, CancellationTokenSource cancellationTokenSource)
+ private async Task<ActionResult> GetVariantPlaylistInternal(StreamingRequestDto streamingRequest, CancellationTokenSource cancellationTokenSource)
{
using var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
@@ -1374,60 +1408,16 @@ namespace Jellyfin.Api.Controllers
cancellationTokenSource.Token)
.ConfigureAwait(false);
- Response.Headers.Add(HeaderNames.Expires, "0");
-
- var segmentLengths = GetSegmentLengths(state);
-
- var segmentContainer = state.Request.SegmentContainer ?? "ts";
-
- // http://ffmpeg.org/ffmpeg-all.html#toc-hls-2
- var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase);
- var hlsVersion = isHlsInFmp4 ? "7" : "3";
-
- var builder = new StringBuilder(128);
-
- builder.AppendLine("#EXTM3U")
- .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
- .Append("#EXT-X-VERSION:")
- .Append(hlsVersion)
- .AppendLine()
- .Append("#EXT-X-TARGETDURATION:")
- .Append(Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength))
- .AppendLine()
- .AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
+ var request = new CreateMainPlaylistRequest(
+ state.MediaPath,
+ state.SegmentLength * 1000,
+ state.RunTimeTicks ?? 0,
+ state.Request.SegmentContainer ?? string.Empty,
+ "hls1/main/",
+ Request.QueryString.ToString());
+ var playlist = _dynamicHlsPlaylistGenerator.CreateMainPlaylist(request);
- var index = 0;
- var segmentExtension = EncodingHelper.GetSegmentFileExtension(streamingRequest.SegmentContainer);
- var queryString = Request.QueryString;
-
- if (isHlsInFmp4)
- {
- builder.Append("#EXT-X-MAP:URI=\"")
- .Append("hls1/")
- .Append(name)
- .Append("/-1")
- .Append(segmentExtension)
- .Append(queryString)
- .Append('"')
- .AppendLine();
- }
-
- foreach (var length in segmentLengths)
- {
- builder.Append("#EXTINF:")
- .Append(length.ToString("0.0000", CultureInfo.InvariantCulture))
- .AppendLine(", nodesc")
- .Append("hls1/")
- .Append(name)
- .Append('/')
- .Append(index++)
- .Append(segmentExtension)
- .Append(queryString)
- .AppendLine();
- }
-
- builder.AppendLine("#EXT-X-ENDLIST");
- return new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8"));
+ return new FileContentResult(Encoding.UTF8.GetBytes(playlist), MimeTypes.GetMimeType("playlist.m3u8"));
}
private async Task<ActionResult> GetDynamicSegment(StreamingRequestDto streamingRequest, int segmentId)
@@ -1528,7 +1518,7 @@ namespace Jellyfin.Api.Controllers
DeleteLastFile(playlistPath, segmentExtension, 0);
}
- streamingRequest.StartTimeTicks = GetStartPositionTicks(state, segmentId);
+ streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks;
state.WaitForPath = segmentPath;
job = await _transcodingJobHelper.StartFfMpeg(
@@ -1879,7 +1869,7 @@ namespace Jellyfin.Api.Controllers
{
// Transcoding job is over, so assume all existing files are ready
_logger.LogDebug("serving up {0} as transcode is over", segmentPath);
- return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
+ return GetSegmentResult(state, segmentPath, transcodingJob);
}
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
@@ -1888,7 +1878,7 @@ namespace Jellyfin.Api.Controllers
if (segmentIndex < currentTranscodingIndex)
{
_logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex);
- return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
+ return GetSegmentResult(state, segmentPath, transcodingJob);
}
}
@@ -1903,8 +1893,8 @@ namespace Jellyfin.Api.Controllers
{
if (transcodingJob.HasExited || System.IO.File.Exists(nextSegmentPath))
{
- _logger.LogDebug("serving up {0} as it deemed ready", segmentPath);
- return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
+ _logger.LogDebug("Serving up {SegmentPath} as it deemed ready", segmentPath);
+ return GetSegmentResult(state, segmentPath, transcodingJob);
}
}
else
@@ -1935,16 +1925,16 @@ namespace Jellyfin.Api.Controllers
_logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath);
}
- return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
+ return GetSegmentResult(state, segmentPath, transcodingJob);
}
- private ActionResult GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJobDto? transcodingJob)
+ private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJobDto? transcodingJob)
{
- var segmentEndingPositionTicks = GetEndPositionTicks(state, index);
+ var segmentEndingPositionTicks = state.Request.CurrentRuntimeTicks + state.Request.ActualSegmentLengthTicks;
Response.OnCompleted(() =>
{
- _logger.LogDebug("finished serving {0}", segmentPath);
+ _logger.LogDebug("Finished serving {SegmentPath}", segmentPath);
if (transcodingJob != null)
{
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
@@ -1957,29 +1947,6 @@ namespace Jellyfin.Api.Controllers
return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath), false, HttpContext);
}
- private long GetEndPositionTicks(StreamState state, int requestedIndex)
- {
- double startSeconds = 0;
- var lengths = GetSegmentLengths(state);
-
- if (requestedIndex >= lengths.Length)
- {
- var msg = string.Format(
- CultureInfo.InvariantCulture,
- "Invalid segment index requested: {0} - Segment count: {1}",
- requestedIndex,
- lengths.Length);
- throw new ArgumentException(msg);
- }
-
- for (var i = 0; i <= requestedIndex; i++)
- {
- startSeconds += lengths[i];
- }
-
- return TimeSpan.FromSeconds(startSeconds).Ticks;
- }
-
private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
{
var job = _transcodingJobHelper.GetTranscodingJob(playlist, TranscodingJobType);
@@ -2058,29 +2025,5 @@ namespace Jellyfin.Api.Controllers
_logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
}
}
-
- private long GetStartPositionTicks(StreamState state, int requestedIndex)
- {
- double startSeconds = 0;
- var lengths = GetSegmentLengths(state);
-
- if (requestedIndex >= lengths.Length)
- {
- var msg = string.Format(
- CultureInfo.InvariantCulture,
- "Invalid segment index requested: {0} - Segment count: {1}",
- requestedIndex,
- lengths.Length);
- throw new ArgumentException(msg);
- }
-
- for (var i = 0; i < requestedIndex; i++)
- {
- startSeconds += lengths[i];
- }
-
- var position = TimeSpan.FromSeconds(startSeconds).Ticks;
- return position;
- }
}
}
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index e72589cfa..aafffc2a1 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -11,12 +12,14 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Branding;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -44,6 +47,8 @@ namespace Jellyfin.Api.Controllers
private readonly IAuthorizationContext _authContext;
private readonly ILogger<ImageController> _logger;
private readonly IServerConfigurationManager _serverConfigurationManager;
+ private readonly IApplicationPaths _appPaths;
+ private readonly IImageEncoder _imageEncoder;
/// <summary>
/// Initializes a new instance of the <see cref="ImageController"/> class.
@@ -56,6 +61,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{ImageController}"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
+ /// <param name="imageEncoder">Instance of the <see cref="IImageEncoder"/> interface.</param>
public ImageController(
IUserManager userManager,
ILibraryManager libraryManager,
@@ -64,7 +71,9 @@ namespace Jellyfin.Api.Controllers
IFileSystem fileSystem,
IAuthorizationContext authContext,
ILogger<ImageController> logger,
- IServerConfigurationManager serverConfigurationManager)
+ IServerConfigurationManager serverConfigurationManager,
+ IApplicationPaths appPaths,
+ IImageEncoder imageEncoder)
{
_userManager = userManager;
_libraryManager = libraryManager;
@@ -74,6 +83,8 @@ namespace Jellyfin.Api.Controllers
_authContext = authContext;
_logger = logger;
_serverConfigurationManager = serverConfigurationManager;
+ _appPaths = appPaths;
+ _imageEncoder = imageEncoder;
}
/// <summary>
@@ -1692,6 +1703,130 @@ namespace Jellyfin.Api.Controllers
.ConfigureAwait(false);
}
+ /// <summary>
+ /// Generates or gets the splashscreen.
+ /// </summary>
+ /// <param name="tag">Supply the cache tag from the item object to receive strong caching headers.</param>
+ /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
+ /// <param name="maxWidth">The maximum image width to return.</param>
+ /// <param name="maxHeight">The maximum image height to return.</param>
+ /// <param name="width">The fixed image width to return.</param>
+ /// <param name="height">The fixed image height to return.</param>
+ /// <param name="fillWidth">Width of box to fill.</param>
+ /// <param name="fillHeight">Height of box to fill.</param>
+ /// <param name="blur">Blur image.</param>
+ /// <param name="backgroundColor">Apply a background color for transparent images.</param>
+ /// <param name="foregroundLayer">Apply a foreground layer on top of the image.</param>
+ /// <param name="quality">Quality setting, from 0-100.</param>
+ /// <response code="200">Splashscreen returned successfully.</response>
+ /// <returns>The splashscreen.</returns>
+ [HttpGet("Branding/Splashscreen")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesImageFile]
+ public async Task<ActionResult> GetSplashscreen(
+ [FromQuery] string? tag,
+ [FromQuery] ImageFormat? format,
+ [FromQuery] int? maxWidth,
+ [FromQuery] int? maxHeight,
+ [FromQuery] int? width,
+ [FromQuery] int? height,
+ [FromQuery] int? fillWidth,
+ [FromQuery] int? fillHeight,
+ [FromQuery] int? blur,
+ [FromQuery] string? backgroundColor,
+ [FromQuery] string? foregroundLayer,
+ [FromQuery, Range(0, 100)] int quality = 90)
+ {
+ var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
+ string splashscreenPath;
+
+ if (!string.IsNullOrWhiteSpace(brandingOptions.SplashscreenLocation)
+ && System.IO.File.Exists(brandingOptions.SplashscreenLocation))
+ {
+ splashscreenPath = brandingOptions.SplashscreenLocation;
+ }
+ else
+ {
+ splashscreenPath = Path.Combine(_appPaths.DataPath, "splashscreen.png");
+ if (!System.IO.File.Exists(splashscreenPath))
+ {
+ return NotFound();
+ }
+ }
+
+ var outputFormats = GetOutputFormats(format);
+
+ TimeSpan? cacheDuration = null;
+ if (!string.IsNullOrEmpty(tag))
+ {
+ cacheDuration = TimeSpan.FromDays(365);
+ }
+
+ var options = new ImageProcessingOptions
+ {
+ Image = new ItemImageInfo
+ {
+ Path = splashscreenPath
+ },
+ Height = height,
+ MaxHeight = maxHeight,
+ MaxWidth = maxWidth,
+ FillHeight = fillHeight,
+ FillWidth = fillWidth,
+ Quality = quality,
+ Width = width,
+ Blur = blur,
+ BackgroundColor = backgroundColor,
+ ForegroundLayer = foregroundLayer,
+ SupportedOutputFormats = outputFormats
+ };
+
+ return await GetImageResult(
+ options,
+ cacheDuration,
+ ImmutableDictionary<string, string>.Empty,
+ Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase))
+ .ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Uploads a custom splashscreen.
+ /// </summary>
+ /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
+ /// <response code="204">Successfully uploaded new splashscreen.</response>
+ /// <response code="400">Error reading MimeType from uploaded image.</response>
+ /// <response code="403">User does not have permission to upload splashscreen..</response>
+ /// <exception cref="ArgumentException">Error reading the image format.</exception>
+ [HttpPost("Branding/Splashscreen")]
+ [Authorize(Policy = Policies.RequiresElevation)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [AcceptsImageFile]
+ public async Task<ActionResult> UploadCustomSplashscreen()
+ {
+ await using var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
+
+ var mimeType = MediaTypeHeaderValue.Parse(Request.ContentType).MediaType;
+
+ if (!mimeType.HasValue)
+ {
+ return BadRequest("Error reading mimetype from uploaded image");
+ }
+
+ var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + MimeTypes.ToExtension(mimeType.Value));
+ var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
+ brandingOptions.SplashscreenLocation = filePath;
+ _serverConfigurationManager.SaveConfiguration("branding", brandingOptions);
+
+ await using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
+ {
+ await memoryStream.CopyToAsync(fs, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ return NoContent();
+ }
+
private static async Task<MemoryStream> GetMemoryStream(Stream inputStream)
{
using var reader = new StreamReader(inputStream);
@@ -1823,25 +1958,35 @@ namespace Jellyfin.Api.Controllers
{ "realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*" }
};
+ if (!imageInfo.IsLocalFile && item != null)
+ {
+ imageInfo = await _libraryManager.ConvertImageToLocal(item, imageInfo, imageIndex ?? 0).ConfigureAwait(false);
+ }
+
+ var options = new ImageProcessingOptions
+ {
+ Height = height,
+ ImageIndex = imageIndex ?? 0,
+ Image = imageInfo,
+ Item = item,
+ ItemId = itemId,
+ MaxHeight = maxHeight,
+ MaxWidth = maxWidth,
+ FillHeight = fillHeight,
+ FillWidth = fillWidth,
+ Quality = quality ?? 100,
+ Width = width,
+ AddPlayedIndicator = addPlayedIndicator ?? false,
+ PercentPlayed = percentPlayed ?? 0,
+ UnplayedCount = unplayedCount,
+ Blur = blur,
+ BackgroundColor = backgroundColor,
+ ForegroundLayer = foregroundLayer,
+ SupportedOutputFormats = outputFormats
+ };
+
return await GetImageResult(
- item,
- itemId,
- imageIndex,
- width,
- height,
- maxWidth,
- maxHeight,
- fillWidth,
- fillHeight,
- quality,
- addPlayedIndicator,
- percentPlayed,
- unplayedCount,
- blur,
- backgroundColor,
- foregroundLayer,
- imageInfo,
- outputFormats,
+ options,
cacheDuration,
responseHeaders,
isHeadRequest).ConfigureAwait(false);
@@ -1921,56 +2066,12 @@ namespace Jellyfin.Api.Controllers
}
private async Task<ActionResult> GetImageResult(
- BaseItem? item,
- Guid itemId,
- int? index,
- int? width,
- int? height,
- int? maxWidth,
- int? maxHeight,
- int? fillWidth,
- int? fillHeight,
- int? quality,
- bool? addPlayedIndicator,
- double? percentPlayed,
- int? unplayedCount,
- int? blur,
- string? backgroundColor,
- string? foregroundLayer,
- ItemImageInfo imageInfo,
- IReadOnlyCollection<ImageFormat> supportedFormats,
+ ImageProcessingOptions imageProcessingOptions,
TimeSpan? cacheDuration,
IDictionary<string, string> headers,
bool isHeadRequest)
{
- if (!imageInfo.IsLocalFile && item != null)
- {
- imageInfo = await _libraryManager.ConvertImageToLocal(item, imageInfo, index ?? 0).ConfigureAwait(false);
- }
-
- var options = new ImageProcessingOptions
- {
- Height = height,
- ImageIndex = index ?? 0,
- Image = imageInfo,
- Item = item,
- ItemId = itemId,
- MaxHeight = maxHeight,
- MaxWidth = maxWidth,
- FillHeight = fillHeight,
- FillWidth = fillWidth,
- Quality = quality ?? 100,
- Width = width,
- AddPlayedIndicator = addPlayedIndicator ?? false,
- PercentPlayed = percentPlayed ?? 0,
- UnplayedCount = unplayedCount,
- Blur = blur,
- BackgroundColor = backgroundColor,
- ForegroundLayer = foregroundLayer,
- SupportedOutputFormats = supportedFormats
- };
-
- var (imagePath, imageContentType, dateImageModified) = await _imageProcessor.ProcessImage(options).ConfigureAwait(false);
+ var (imagePath, imageContentType, dateImageModified) = await _imageProcessor.ProcessImage(imageProcessingOptions).ConfigureAwait(false);
var disableCaching = Request.Headers[HeaderNames.CacheControl].Contains("no-cache");
var parsingSuccessful = DateTime.TryParse(Request.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index a6c2e07c9..e9d48b624 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -341,10 +341,7 @@ namespace Jellyfin.Api.Controllers
{
var list = items;
- var result = new QueryResult<BaseItemDto>
- {
- TotalRecordCount = list.Count
- };
+ var totalCount = list.Count;
if (limit.HasValue && limit < list.Count)
{
@@ -353,7 +350,10 @@ namespace Jellyfin.Api.Controllers
var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user);
- result.Items = returnList;
+ var result = new QueryResult<BaseItemDto>(
+ 0,
+ totalCount,
+ returnList);
return result;
}
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index 4161e43f6..c49f85616 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -263,7 +263,8 @@ namespace Jellyfin.Api.Controllers
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
ReplaceAllMetadata = true,
ReplaceAllImages = replaceAllImages,
- SearchResult = searchResult
+ SearchResult = searchResult,
+ RemoveOldMetadata = true
},
CancellationToken.None).ConfigureAwait(false);
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index f8192955e..dc7af0a20 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -491,10 +491,13 @@ namespace Jellyfin.Api.Controllers
else
{
var itemsArray = folder.GetChildren(user, true);
- result = new QueryResult<BaseItem> { Items = itemsArray, TotalRecordCount = itemsArray.Count, StartIndex = 0 };
+ result = new QueryResult<BaseItem>(itemsArray);
}
- return new QueryResult<BaseItemDto> { StartIndex = startIndex.GetValueOrDefault(), TotalRecordCount = result.TotalRecordCount, Items = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user) };
+ return new QueryResult<BaseItemDto>(
+ startIndex,
+ result.TotalRecordCount,
+ _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user));
}
/// <summary>
@@ -836,12 +839,10 @@ namespace Jellyfin.Api.Controllers
var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, dtoOptions, user);
- return new QueryResult<BaseItemDto>
- {
- StartIndex = startIndex.GetValueOrDefault(),
- TotalRecordCount = itemsResult.TotalRecordCount,
- Items = returnItems
- };
+ return new QueryResult<BaseItemDto>(
+ startIndex,
+ itemsResult.TotalRecordCount,
+ returnItems);
}
}
}
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index f1b9c2f67..c65462ab5 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -506,13 +506,8 @@ namespace Jellyfin.Api.Controllers
}
var dtoOptions = new DtoOptions().AddClientFields(Request);
- var result = new QueryResult<BaseItemDto>
- {
- TotalRecordCount = items.Count,
- Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray()
- };
-
- return result;
+ var resultArray = _dtoService.GetBaseItemDtos(items, dtoOptions);
+ return new QueryResult<BaseItemDto>(resultArray);
}
/// <summary>
@@ -759,11 +754,10 @@ namespace Jellyfin.Api.Controllers
var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
- return new QueryResult<BaseItemDto>
- {
- Items = returnList,
- TotalRecordCount = itemsResult.Count
- };
+ return new QueryResult<BaseItemDto>(
+ query.StartIndex,
+ itemsResult.Count,
+ returnList);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 9e2ef8c60..484b0a974 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -193,11 +193,10 @@ namespace Jellyfin.Api.Controllers
dtoOptions.AddCurrentProgram = addCurrentProgram;
var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, dtoOptions, user);
- return new QueryResult<BaseItemDto>
- {
- Items = returnArray,
- TotalRecordCount = channelResult.TotalRecordCount
- };
+ return new QueryResult<BaseItemDto>(
+ startIndex,
+ channelResult.TotalRecordCount,
+ returnArray);
}
/// <summary>
@@ -390,11 +389,7 @@ namespace Jellyfin.Api.Controllers
var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user);
- return new QueryResult<BaseItemDto>
- {
- Items = returnArray,
- TotalRecordCount = returnArray.Count
- };
+ return new QueryResult<BaseItemDto>(returnArray);
}
/// <summary>
@@ -687,7 +682,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Programs/Recommended")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QueryResult<BaseItemDto>> GetRecommendedPrograms(
+ public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms(
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery] bool? isAiring,
@@ -726,7 +721,7 @@ namespace Jellyfin.Api.Controllers
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
- return _liveTvManager.GetRecommendedPrograms(query, dtoOptions, CancellationToken.None);
+ return await _liveTvManager.GetRecommendedProgramsAsync(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index cb4894d77..ffc748a6e 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -101,11 +101,7 @@ namespace Jellyfin.Api.Controllers
Limit = limit ?? 0
});
- return new QueryResult<BaseItemDto>
- {
- Items = peopleItems.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)).ToArray(),
- TotalRecordCount = peopleItems.Count
- };
+ return new QueryResult<BaseItemDto>(peopleItems.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)).ToArray());
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index 1667d6ede..c18f1b427 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -208,11 +208,10 @@ namespace Jellyfin.Api.Controllers
dtos[index].PlaylistItemId = items[index].Item1.Id;
}
- var result = new QueryResult<BaseItemDto>
- {
- Items = dtos,
- TotalRecordCount = count
- };
+ var result = new QueryResult<BaseItemDto>(
+ startIndex,
+ count,
+ dtos);
return result;
}
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index af77c801f..73be26bb2 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -81,11 +81,10 @@ namespace Jellyfin.Api.Controllers
var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user);
- return new QueryResult<BaseItemDto>
- {
- TotalRecordCount = result.TotalRecordCount,
- Items = dtoList
- };
+ return new QueryResult<BaseItemDto>(
+ startIndex,
+ result.TotalRecordCount,
+ dtoList);
}
}
}
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index e20bcd7a7..9425fe519 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -110,11 +110,10 @@ namespace Jellyfin.Api.Controllers
var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user);
- return new QueryResult<BaseItemDto>
- {
- TotalRecordCount = result.TotalRecordCount,
- Items = returnItems
- };
+ return new QueryResult<BaseItemDto>(
+ startIndex,
+ result.TotalRecordCount,
+ returnItems);
}
/// <summary>
@@ -169,11 +168,10 @@ namespace Jellyfin.Api.Controllers
var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user);
- return new QueryResult<BaseItemDto>
- {
- TotalRecordCount = itemsResult.Count,
- Items = returnItems
- };
+ return new QueryResult<BaseItemDto>(
+ startIndex,
+ itemsResult.Count,
+ returnItems);
}
/// <summary>
@@ -296,11 +294,10 @@ namespace Jellyfin.Api.Controllers
var dtos = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
- return new QueryResult<BaseItemDto>
- {
- TotalRecordCount = episodes.Count,
- Items = dtos
- };
+ return new QueryResult<BaseItemDto>(
+ startIndex,
+ episodes.Count,
+ dtos);
}
/// <summary>
@@ -354,11 +351,7 @@ namespace Jellyfin.Api.Controllers
var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user);
- return new QueryResult<BaseItemDto>
- {
- TotalRecordCount = returnItems.Count,
- Items = returnItems
- };
+ return new QueryResult<BaseItemDto>(returnItems);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index 8b99170d9..008d2f176 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -124,11 +124,7 @@ namespace Jellyfin.Api.Controllers
var dtoOptions = new DtoOptions().AddClientFields(Request);
var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
- return new QueryResult<BaseItemDto>
- {
- Items = dtos,
- TotalRecordCount = dtos.Length
- };
+ return new QueryResult<BaseItemDto>(dtos);
}
/// <summary>
@@ -206,21 +202,16 @@ namespace Jellyfin.Api.Controllers
: _libraryManager.GetItemById(itemId);
var dtoOptions = new DtoOptions().AddClientFields(Request);
- var dtosExtras = item.GetExtras(new[] { ExtraType.Trailer })
- .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
- .ToArray();
if (item is IHasTrailers hasTrailers)
{
var trailers = hasTrailers.LocalTrailers;
- var dtosTrailers = _dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item);
- var allTrailers = new BaseItemDto[dtosExtras.Length + dtosTrailers.Count];
- dtosExtras.CopyTo(allTrailers, 0);
- dtosTrailers.CopyTo(allTrailers, dtosExtras.Length);
- return allTrailers;
+ return Ok(_dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item));
}
- return dtosExtras;
+ return Ok(item.GetExtras()
+ .Where(e => e.ExtraType == ExtraType.Trailer)
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)));
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs
index 3d27371f6..04171da8a 100644
--- a/Jellyfin.Api/Controllers/UserViewsController.cs
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -108,11 +108,7 @@ namespace Jellyfin.Api.Controllers
var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
.ToArray();
- return new QueryResult<BaseItemDto>
- {
- Items = dtos,
- TotalRecordCount = dtos.Length
- };
+ return new QueryResult<BaseItemDto>(dtos);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 3c079a71d..89b150598 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -134,12 +134,7 @@ namespace Jellyfin.Api.Controllers
items = Array.Empty<BaseItemDto>();
}
- var result = new QueryResult<BaseItemDto>
- {
- Items = items,
- TotalRecordCount = items.Length
- };
-
+ var result = new QueryResult<BaseItemDto>(items);
return result;
}
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index 8be6fd1b5..bac77d43b 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -136,8 +136,6 @@ namespace Jellyfin.Api.Controllers
IEnumerable<BaseItem> ibnItems = ibnItemsArray;
- var result = new QueryResult<BaseItemDto> { TotalRecordCount = ibnItemsArray.Count };
-
if (startIndex.HasValue || limit.HasValue)
{
if (startIndex.HasValue)
@@ -155,8 +153,10 @@ namespace Jellyfin.Api.Controllers
var dtos = tuples.Select(i => _dtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user));
- result.Items = dtos.Where(i => i != null).ToArray();
-
+ var result = new QueryResult<BaseItemDto>(
+ startIndex,
+ ibnItemsArray.Count,
+ dtos.Where(i => i != null).ToArray());
return result;
}